PHP和枚举

PHP and Enumerations

我知道PHP没有本机枚举。但我已经习惯了Java世界。我希望使用枚举作为一种方法来提供IDES的自动完成特性可以理解的预定义值。

常量可以做到这一点,但存在名称空间冲突问题,而且(或者实际上是因为)它们是全局的。数组没有名称空间问题,但是它们太模糊了,它们可以在运行时被覆盖,IDE很少(从不?)知道如何自动填充钥匙。

您是否经常使用任何解决方案/解决方案?是否有人记得PHP人员对Enums有什么想法或决定?


根据用例的不同,我通常使用如下简单的方法:

1
2
3
4
5
6
7
8
abstract class DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

$today = DaysOfWeek::Sunday;

但是,其他用例可能需要对常量和值进行更多的验证。基于下面关于反思的评论和其他一些注释,这里有一个扩展的示例,它可以更好地服务于更广泛的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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的简单枚举类,您现在可以使用方法来进行简单的输入验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

作为补充说明,每当我在一个静态/常量类上至少使用一次反射(例如在枚举中),我缓存这些反射调用的结果,因为每次使用新的反射对象最终都会对性能产生显著影响(存储在多个枚举的Associative数组中)。

既然大多数人最终都升级到了至少5.3,并且可以使用SplEnum,那么这当然是一个可行的选择——只要您不介意传统的非结构化概念,即在整个代码库中使用实际的枚举实例化。在上面的例子中,BasicEnumDaysOfWeek根本不能被实例化,也不应该被实例化。


还有一个本机扩展。脾

SplEnum gives the ability to emulate and create enumeration objects
natively in PHP.

http://www.php.net/manual/en/class.splenum.php


类常量呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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


上面的答案很好。但是,如果您以两种不同的方式来extend它,那么无论哪个扩展先完成,都会导致对函数的调用,从而创建缓存。该缓存将被所有后续调用使用,无论调用由哪个扩展启动…

要解决此问题,请将变量和第一个函数替换为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
eflectionClass($calledClass);
        self::$constCacheArray[$calledClass] = $reflect->getConstants();
    }

    return self::$constCacheArray[$calledClass];
}


我用interface代替class

1
2
3
4
5
6
7
8
interface DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

var $today = DaysOfWeek::Sunday;


我使用了带有常量的类:

1
2
3
4
5
6
class Enum {
    const NAME       = 'aaaa';
    const SOME_VALUE = 'bbbb';
}

print Enum::NAME;


我已经对这里的其他一些答案发表了评论,所以我想我也会参与进来。最后,由于PHP不支持类型化枚举,您可以采用以下两种方法之一:黑客类型化枚举,或者使用非常难以有效黑客的事实。

我更愿意接受事实,而不是使用其他答案以某种方式使用的const方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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());
    }

}

枚举示例:

1
2
3
4
5
6
7
8
9
10
11
12
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作为基类,所有其他枚举都可以从中扩展,从而允许辅助方法,如toArrayisValid等。对我来说,类型化的枚举(和管理它们的实例)最终太混乱了。

假设

如果存在一种__getStatic魔法方法(最好也有一种__equals魔法方法),那么可以用一种多子模式来减轻这种情况。

(以下是假设性的;虽然可能有一天它会起作用,但它不会起作用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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


对于PHP中的一个简单的Java类枚举,我使用:

1
2
3
4
5
6
7
8
9
10
11
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];
    }
}

并称之为:

1
2
SomeTypeName::toOrdinal("Read");
SomeTypeName::toString(1);

但我是一个PHP初学者,在语法方面很吃力,所以这可能不是最好的方法。我用类常量做了一些实验,使用反射从它的值中获取常量名,可能会更整洁。


四年后我又遇到了这个问题。我目前的方法是这样的,因为它允许代码在IDE中完成以及类型安全:

基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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;
    }
}

实例枚举:

1
2
3
4
5
6
7
8
9
10
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); }      
}

示例用法:

1
2
3
4
5
6
7
8
9
10
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');

请注意,相同枚举项的所有实例都是相同的:

1
2
3
$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true

您也可以在switch语句中使用它:

1
2
3
4
5
6
7
8
function getGermanWeekDayName(DaysOfWeek $weekDay)
{
    switch ($weekDay)
    {
        case DaysOfWeek::Monday(): return 'Montag';
        case DaysOfWeek::Tuesday(): return 'Dienstag';
        // ...
}

还可以按名称或值创建枚举项:

1
2
$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');

或者,您只需从现有枚举项中获取名称(即函数名):

1
2
$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday


我也喜欢Java中的枚举,因此我用这种方式编写我的枚举,我认为这是Java枚举中最相似的行为,当然,如果有人想从Java中使用更多的方法,应该在这里写,或者在抽象类中编写,但是核心思想嵌入到下面的代码中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class FruitsEnum {

    static $APPLE = null;
    static $ORANGE = null;

    private $value = null;

    public static $map;

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

    public static function init () {
        self::$APPLE  = new FruitsEnum("Apple");
        self::$ORANGE = new FruitsEnum("Orange");
        //static map to get object by name - example Enum::get("INIT") - returns Enum::$INIT object;
        self::$map = array (
           "Apple" => self::$APPLE,
           "Orange" => self::$ORANGE
        );
    }

    public static function get($element) {
        if($element == null)
            return null;
        return self::$map[$element];
    }

    public function getValue() {
        return $this->value;
    }

    public function equals(FruitsEnum $element) {
        return $element->getValue() == $this->getValue();
    }

    public function __toString () {
        return $this->value;
    }
}
FruitsEnum::init();

var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$APPLE)); //true
var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$ORANGE)); //false
var_dump(FruitsEnum::$APPLE instanceof FruitsEnum); //true
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::$APPLE)); //true - enum from string
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::get("Orange"))); //false


我在Github上找到了这个库,我认为它为这里的答案提供了一个非常好的选择。

php枚举实现灵感来自splenum

  • 您可以输入提示:function setAction(Action $action) {
  • 您可以使用方法(例如formatparse…)丰富枚举。
  • 您可以扩展枚举以添加新值(使枚举final避免它)
  • 您可以获得所有可能值的列表(见下文)

宣言

1
2
3
4
5
6
7
8
9
10
11
<?php
use MyCLabs\Enum\Enum;

/**
 * Action enum
 */

class Action extends Enum
{
    const VIEW = 'view';
    const EDIT = 'edit';
}

用法

1
2
3
4
5
<?php
$action = new Action(Action::VIEW);

// or
$action = Action::VIEW();

类型提示枚举值:

1
2
3
4
<?php
function setAction(Action $action) {
    // ...
}


如果需要使用全局唯一的枚举(即即使在比较不同枚举之间的元素时也是如此),并且易于使用,请随意使用以下代码。我还添加了一些我觉得有用的方法。您可以在代码顶部的注释中找到示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php

/**
 * Class Enum
 *
 * @author Christopher Fox <[email protected]>
 *
 * @version 1.0
 *
 * This class provides the function of an enumeration.
 * The values of Enum elements are unique (even between different Enums)
 * as you would expect them to be.
 *
 * Constructing a new Enum:
 * ========================
 *
 * In the following example we construct an enum called"UserState"
 * with the elements"inactive","active","banned" and"deleted".
 *
 * <wyn>
 * Enum::Create('UserState', 'inactive', 'active', 'banned', 'deleted');
 * </wyn>
 *
 * Using Enums:
 * ============
 *
 * The following example demonstrates how to compare two Enum elements
 *
 * <wyn>
 * var_dump(UserState::inactive == UserState::banned); // result: false
 * var_dump(UserState::active == UserState::active); // result: true
 * </wyn>
 *
 * Special Enum methods:
 * =====================
 *
 * Get the number of elements in an Enum:
 *
 * <wyn>
 * echo UserState::CountEntries(); // result: 4
 * </wyn>
 *
 * Get a list with all elements of the Enum:
 *
 * <wyn>
 * $allUserStates = UserState::GetEntries();
 * </wyn>
 *
 * Get a name of an element:
 *
 * <wyn>
 * echo UserState::GetName(UserState::deleted); // result: deleted
 * </wyn>
 *
 * Get an integer ID for an element (e.g. to store as a value in a database table):
 * This is simply the index of the element (beginning with 1).
 * Note that this ID is only unique for this Enum but now between different Enums.
 *
 * <wyn>
 * echo UserState::GetDatabaseID(UserState::active); // result: 2
 * </wyn>
 */

class Enum
{

    /**
     * @var Enum $instance The only instance of Enum (Singleton)
     */

    private static $instance;

    /**
     * @var array $enums    An array of all enums with Enum names as keys
     *          and arrays of element names as values
     */

    private $enums;

    /**
     * Constructs (the only) Enum instance
     */

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

    /**
     * Constructs a new enum
     *
     * @param string $name The class name for the enum
     * @param mixed $_ A list of strings to use as names for enum entries
     */

    public static function Create($name, $_)
    {
        // Create (the only) Enum instance if this hasn't happened yet
        if (self::$instance===null)
        {
            self::$instance = new Enum();
        }

        // Fetch the arguments of the function
        $args = func_get_args();
        // Exclude the"name" argument from the array of function arguments,
        // so only the enum element names remain in the array
        array_shift($args);
        self::$instance->add($name, $args);
    }

    /**
     * Creates an enumeration if this hasn't happened yet
     *
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     */

    private function add($name, $fields)
    {
        if (!array_key_exists($name, $this->enums))
        {
            $this->enums[$name] = array();

            // Generate the code of the class for this enumeration
            $classDeclaration =    "class" . $name ." {
"

                        ."private static \$name = '" . $name ."';
"

                        . $this->getClassConstants($name, $fields)
                        . $this->getFunctionGetEntries($name)
                        . $this->getFunctionCountEntries($name)
                        . $this->getFunctionGetDatabaseID()
                        . $this->getFunctionGetName()
                        .
<div class="suo-content">[collapse title=""]<ul><li>我很喜欢这个。但是,主要的抱怨之一是IDE能够获取自动完成的值。我不确定如果没有IDE的自定义插件,这是否能够做到。不是说做不到,只是需要一些工作。</li><li>使用<wyn>eval()</wyn>以便声明新的枚举运行时?EEK。我没有感觉到。在定义正确的枚举类之前,如何防止其他类创建不正确的枚举类?枚举在运行时之前不是已知的吗?正如@corsika所暗示的,没有IDE自动完成。我看到的唯一好处就是懒惰的编码。</li></ul>[/collapse]</div><hr>[cc lang="php"]abstract class Enumeration
{
    public static function enum()
    {
        $reflect = new ReflectionClass( get_called_class() );
        return $reflect->getConstants();
    }
}


class Test extends Enumeration
{
    const A = 'a';
    const B = 'b';    
}


foreach (Test::enum() as $key => $value) {
    echo"$key -> $value";
}

可能很简单

1
2
3
4
5
enum DaysOfWeek {
    Sunday,
    Monday,
    // ...
}

未来。

php rfc:枚举类型


Here is a github library for handling type-safe enumerations in php:

This library handle classes generation, classes caching and it implements the Type Safe Enumeration design pattern, with several helper methods for dealing with enums, like retrieving an ordinal for enums sorting, or retrieving a binary value, for enums combinations.

The generated code use a plain old php template file, which is also configurable, so you can provide your own template.

It is full test covered with phpunit.

php-enums on github (feel free to fork)

Usage: (@see usage.php, or unit tests for more details)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
//require the library
require_once __DIR__ . '/src/Enum.func.php';

//if you don't have a cache directory, create one
@mkdir(__DIR__ . '/cache');
EnumGenerator::setDefaultCachedClassesDir(__DIR__ . '/cache');

//Class definition is evaluated on the fly:
Enum('FruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'));

//Class definition is cached in the cache directory for later usage:
Enum('CachedFruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'), '\my\company
ame\space'
, true);

echo 'FruitsEnum::APPLE() == FruitsEnum::APPLE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::APPLE()) ."
"
;

echo 'FruitsEnum::APPLE() == FruitsEnum::ORANGE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::ORANGE()) ."
"
;

echo 'FruitsEnum::APPLE() instanceof Enum: ';
var_dump(FruitsEnum::APPLE() instanceof Enum) ."
"
;

echo 'FruitsEnum::APPLE() instanceof FruitsEnum: ';
var_dump(FruitsEnum::APPLE() instanceof FruitsEnum) ."
"
;

echo"->getName()
"
;
foreach (FruitsEnum::iterator() as $enum)
{
  echo" " . $enum->getName() ."
"
;
}

echo"->getValue()
"
;
foreach (FruitsEnum::iterator() as $enum)
{
  echo" " . $enum->getValue() ."
"
;
}

echo"->getOrdinal()
"
;
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo" " . $enum->getOrdinal() ."
"
;
}

echo"->getBinary()
"
;
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo" " . $enum->getBinary() ."
"
;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FruitsEnum::APPLE() == FruitsEnum::APPLE(): bool(true)
FruitsEnum::APPLE() == FruitsEnum::ORANGE(): bool(false)
FruitsEnum::APPLE() instanceof Enum: bool(true)
FruitsEnum::APPLE() instanceof FruitsEnum: bool(true)
->getName()
  APPLE
  ORANGE
  RASBERRY
  BANNANA
->getValue()
  apple
  orange
  rasberry
  bannana
->getValue() when values have been specified
  pig
  dog
  cat
  bird
->getOrdinal()
  1
  2
  3
  4
->getBinary()
  1
  2
  4
  8

我在PHP中看到的最常见的解决方案是创建一个通用的枚举类,然后扩展它。你可以看看这个。

更新:或者,我在phpclasses.org上找到了这个。


我意识到这是一条非常古老的线,但我有一个想法,想知道人们的想法。

注:我在玩这个,我意识到如果我只是修改了__call()函数,你可以更接近实际的enums__call()函数处理所有未知的函数调用。所以假设你想做三个enums红灯、黄灯和绿灯。现在只需执行以下操作即可完成此操作:

1
2
3
$c->RED_LIGHT();
$c->YELLOW_LIGHT();
$c->GREEN_LIGHT();

一旦定义好,您所要做的就是再次调用它们以获取值:

1
2
3
echo $c->RED_LIGHT();
echo $c->YELLOW_LIGHT();
echo $c->GREEN_LIGHT();

你应该得到0,1和2。玩得高兴!现在Github上也有这个功能。

更新:我已经做了,所以现在使用__get()__set()函数。这些允许您不必调用函数,除非您愿意。相反,现在你可以说:

1
2
3
$c->RED_LIGHT;
$c->YELLOW_LIGHT;
$c->GREEN_LIGHT;

对于价值的创造和获取。因为变量最初没有定义,所以调用__get()函数(因为没有指定值),该函数会看到数组中的条目没有被生成。因此,它生成条目,为其指定最后一个给定值加上一个(+1),增加最后一个值变量,并返回true。如果设置该值:

1
$c->RED_LIGHT = 85;

然后调用__set()函数,最后一个值设置为新值加上一个(+1)。所以现在我们有了一个相当好的方法来做枚举,它们可以被动态创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<?php
################################################################################
#   Class ENUMS
#
#       Original code by Mark Manning.
#       Copyrighted (c) 2015 by Mark Manning.
#       All rights reserved.
#
#       This set of code is hereby placed into the free software universe
#       via the GNU greater license thus placing it under the Copyleft
#       rules and regulations with the following modifications:
#
#       1. You may use this work in any other work.  Commercial or otherwise.
#       2. You may make as much money as you can with it.
#       3. You owe me nothing except to give me a small blurb somewhere in
#           your program or maybe have pity on me and donate a dollar to
#           [email protected].  :-)
#
#   Blurb:
#
#       PHP Class Enums by Mark Manning (markem-AT-sim1-DOT-us).
#       Used with permission.
#
#   Notes:
#
#       VIM formatting.  Set tabs to four(4) spaces.
#
################################################################################
class enums
{
    private $enums;
    private $clear_flag;
    private $last_value;

################################################################################
#   __construct(). Construction function.  Optionally pass in your enums.
################################################################################
function __construct()
{
    $this->enums = array();
    $this->clear_flag = false;
    $this->last_value = 0;

    if( func_num_args() > 0 ){
        return $this->put( func_get_args() );
        }

    return true;
}
################################################################################
#   put(). Insert one or more enums.
################################################################################
function put()
{
    $args = func_get_args();
#
#   Did they send us an array of enums?
#   Ex: $c->put( array("a"=>0,"b"=>1,...) );
#   OR  $c->put( array("a","b","c",... ) );
#
   if( is_array($args[0]) ){
#
#   Add them all in
#
       foreach( $args[0] as $k=>$v ){
#
#   Don't let them change it once it is set.
#   Remove the IF statement if you want to be able to modify the enums.
#
           if( !isset($this->enums[$k]) ){
#
#   If they sent an array of enums like this:"a","b","c",... then we have to
#   change that to be"A"=>#. Where"#" is the current count of the enums.
#
               if( is_numeric($k) ){
                    $this->enums[$v] = $this->last_value++;
                    }
#
#   Else - they sent"a"=>"A","b"=>"B","c"=>"C"...
#
                   else {
                        $this->last_value = $v + 1;
                        $this->enums[$k] = $v;
                        }
                }
            }
        }
#
#   Nope!  Did they just sent us one enum?
#
       else {
#
#   Is this just a default declaration?
#   Ex: $c->put("a" );
#
           if( count($args) < 2 ){
#
#   Again - remove the IF statement if you want to be able to change the enums.
#
               if( !isset($this->enums[$args[0]]) ){
                    $this->enums[$args[0]] = $this->last_value++;
                    }
#
#   No - they sent us a regular enum
#   Ex: $c->put("a","This is the first enum" );
#
                   else {
#
#   Again - remove the IF statement if you want to be able to change the enums.
#
                       if( !isset($this->enums[$args[0]]) ){
                            $this->last_value = $args[1] + 1;
                            $this->enums[$args[0]] = $args[1];
                            }
                        }
                }
            }

    return true;
}
################################################################################
#   get(). Get one or more enums.
################################################################################
function get()
{
    $num = func_num_args();
    $args = func_get_args();
#
#   Is this an array of enums request? (ie: $c->get(array("a","b","c"...)) )
#
   if( is_array($args[0]) ){
        $ary = array();
        foreach( $args[0] as $k=>$v ){
            $ary[$v] = $this->enums[$v];
            }

        return $ary;
        }
#
#   Is it just ONE enum they want? (ie: $c->get("a") )
#
       else if( ($num > 0) && ($num < 2) ){
            return $this->enums[$args[0]];
            }
#
#   Is it a list of enums they want? (ie: $c->get("a","b","c"...) )
#
       else if( $num > 1 ){
            $ary = array();
            foreach( $args as $k=>$v ){
                $ary[$v] = $this->enums[$v];
                }

            return $ary;
            }
#
#   They either sent something funky or nothing at all.
#
   return false;
}
################################################################################
#   clear(). Clear out the enum array.
#       Optional.  Set the flag in the __construct function.
#       After all, ENUMS are supposed to be constant.
################################################################################
function clear()
{
    if( $clear_flag ){
        unset( $this->enums );
        $this->enums = array();
        }

    return true;
}
################################################################################
#   __call().  In case someone tries to blow up the class.
################################################################################
function __call( $name, $arguments )
{
    if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
        else if( !isset($this->enums[$name]) && (count($arguments) > 0) ){
            $this->last_value = $arguments[0] + 1;
            $this->enums[$name] = $arguments[0];
            return true;
            }
        else if( !isset($this->enums[$name]) && (count($arguments) < 1) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __get(). Gets the value.
################################################################################
function __get($name)
{
    if( isset($this->enums[$name]) ){ return $this->enums[$name]; }
        else if( !isset($this->enums[$name]) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __set().  Sets the value.
################################################################################
function __set( $name, $value=null )
{
    if( isset($this->enums[$name]) ){ return false; }
        else if( !isset($this->enums[$name]) && !is_null($value) ){
            $this->last_value = $value + 1;
            $this->enums[$name] = $value;
            return true;
            }
        else if( !isset($this->enums[$name]) && is_null($value) ){
            $this->enums[$name] = $this->last_value++;
            return true;
            }

    return false;
}
################################################################################
#   __destruct().  Deconstruct the class.  Remove the list of enums.
################################################################################
function __destruct()
{
    unset( $this->enums );
    $this->enums = null;

    return true;
}

}
#
#   Test code
#
#   $c = new enums();
#   $c->RED_LIGHT(85);
#   $c->YELLOW_LIGHT = 23;
#   $c->GREEN_LIGHT;
#
#   echo $c->RED_LIGHT ."
";
#   echo $c->YELLOW_LIGHT ."

";
#   echo $c->GREEN_LIGHT ."

";

?>

我已经开始使用下面的方法,因为它使我能够为函数参数提供类型安全性、在NetBeans中自动完成以及良好的性能。有一件事我不太喜欢,那就是在定义了类之后,必须调用[extended class name]::enumerate();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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");


现在您可以使用splenum类以本机方式构建它。根据官方文件。

SplEnum gives the ability to emulate and create enumeration objects
natively in PHP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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站点。


我知道这是一个旧线程,但是我看到的所有解决方法都不像枚举,因为几乎所有的解决方法都要求您手动为枚举项赋值,或者需要您将枚举键数组传递给函数。所以我为这个创建了自己的解决方案。

要使用我的解决方案创建枚举类,可以在下面简单地扩展这个枚举类,创建一组静态变量(不需要初始化它们),并在枚举类定义的正下方调用your enum class::init()。

编辑:这只能在php>=5.3中使用,但也可以修改为在旧版本中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * 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;
                }
            }
        }
    }
}

下面的枚举类定义是强类型的,使用和定义非常自然。

定义:

1
2
3
4
5
class Fruit extends Enum {
    static public $APPLE = 1;
    static public $ORANGE = 2;
}
Fruit::initialize(); //Can also be called in autoloader

切换枚举

1
2
3
4
5
6
7
8
9
10
$myFruit = Fruit::$APPLE;

switch ($myFruit) {
    case Fruit::$APPLE  : echo"I like apples
"
;  break;
    case Fruit::$ORANGE : echo"I hate oranges
"
; break;
}

>> I like apples

将枚举作为参数传递(强类型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Function only accepts Fruit enums as input**/
function echoFruit(Fruit $fruit) {
    echo $fruit->getName().":".$fruit->getValue()."
"
;
}

/** Call function with each Enum value that Fruit has */
foreach (Fruit::getList() as $fruit) {
    echoFruit($fruit);
}

//Call function with Apple enum
echoFruit(Fruit::$APPLE)

//Will produce an error. This solution is strongly typed
echoFruit(2);

>> APPLE: 1
>> ORANGE: 2
>> APPLE: 1
>> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given

将枚举作为字符串回送

1
2
3
4
echo"I have an $myFruit
"
;

>> I have an APPLE

按整数获取枚举

1
2
3
4
5
6
$myFruit = Fruit::getByValue(2);

echo"Now I have an $myFruit
"
;

>> Now I have an ORANGE

按名称获取枚举

1
2
3
4
5
6
7
$myFruit = Fruit::getByName("APPLE");

echo"But I definitely prefer an $myFruit

"
;

>> But I definitely prefer an APPLE

枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
 * @author Torge Kummerow
 */

class Enum {

    /**
     * Holds the values for each type of Enum
     */

    static private $list = array();

    /**
     * Initializes the enum values by replacing the number with an instance of itself
     * using reflection
     */

    static public function initialize() {
        $className = get_called_class();
        $class = new ReflectionClass($className);
        $staticProperties = $class->getStaticProperties();

        self::$list[$className] = array();

        foreach ($staticProperties as $propertyName => &$value) {
            if ($propertyName == 'list')
                continue;

            $enum = new $className($propertyName, $value);
            $class->setStaticPropertyValue($propertyName, $enum);
            self::$list[$className][$propertyName] = $enum;
        } unset($value);
    }


    /**
     * Gets the enum for the given value
     *
     * @param integer $value
     * @throws Exception
     *
     * @return Enum
     */

    static public function getByValue($value) {
        $className = get_called_class();
        foreach (self::$list[$className] as $propertyName=>&$enum) {
            /* @var $enum Enum */
            if ($enum->value == $value)
                return $enum;
        } unset($enum);

        throw new Exception("No such enum with value=$value of type".get_called_class());
    }

    /**
     * Gets the enum for the given name
     *
     * @param string $name
     * @throws Exception
     *
     * @return Enum
     */

    static public function getByName($name) {
        $className = get_called_class();
        if (array_key_exists($name, static::$list[$className]))
            return self::$list[$className][$name];

        throw new Exception("No such enum".get_called_class()."::\$$name");
    }


    /**
     * Returns the list of all enum variants
     * @return Array of Enum
     */

    static public function getList() {
        $className = get_called_class();
        return self::$list[$className];
    }


    private $name;
    private $value;

    public function __construct($name, $value) {
        $this->name = $name;
        $this->value = $value;
    }

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

    public function getValue() {
        return $this->value;
    }

    public function getName() {
        return $this->name;
    }

}

加法

当然,您也可以为IDES添加注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fruit extends Enum {

    /**
     * This comment is for autocomplete support in common IDEs
     * @var Fruit A yummy apple
     */

    static public $APPLE = 1;

    /**
     * This comment is for autocomplete support in common IDEs
     * @var Fruit A sour orange
     */

    static public $ORANGE = 2;
}

//This can also go to the autoloader if available.
Fruit::initialize();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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内置的东西)。


在回答@brian cline时,我想我可能会给我5美分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<?php
/**
 * A class that simulates Enums behaviour
 * <wyn>
 * 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
 * </wyn>
 *
 * 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
     * <wyn>
     * // 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
     * </wyn>
     *
     * @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
eflectionClass($calledClass);
            self::$_constCacheArray[$calledClass] = $reflect->getConstants();
        }
        return self::$_constCacheArray[$calledClass];
    }
}


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

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

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

然后,您不能在函数参数中键入hint,因为它不可实例化,也因为EDOCX1的类型(0)是int,而不是ShirtSize

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

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
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://stackoverflow.com/a/25526473/2407870。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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类中使用常量可能会导致误用:例如,可能会错误地使用它:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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对象转换为其整数表示形式。

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

希望这会有帮助


这里有一些好的解决方案!

这是我的版本。

  • 它是强类型的
  • 它与IDE自动完成一起工作
  • 枚举由代码和描述定义,其中代码可以是整数、二进制值、短字符串,或者基本上是您想要的任何内容。该模式可以很容易地扩展以支持更高的属性。
  • 它支持值(==)和引用(==)比较,并在switch语句中工作。

我认为主要的缺点是枚举成员必须单独声明和实例化,因为描述和PHP无法在静态成员声明时构造对象。我想解决这个问题的一种方法可能是将反射与解析的文档注释一起使用。

抽象枚举如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?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;
    }
}

下面是一个具体枚举示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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();

可以这样使用:

1
2
3
4
5
6
7
8
9
<?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


最后,一个php 7.1+的答案带有不能重写的常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * An interface that groups HTTP Accept: header Media Types in one place.
 */

interface AcceptMediaTypes
{
    /**
    * Now, if you have to use these same constants with another class, you can
    * without creating funky inheritance / is-a relationships.
    * Also, this gets around the single inheritance limitation.
    */


    public const HTML = 'text/html';
    public const JSON = 'application/json';
    public const XML = 'application/xml';
    public const TEXT = 'text/plain';
}

/**
 * An generic request class.
 */

abstract class Request
{
    // Why not put the constants here?
    // 1) The logical reuse issue.
    // 2) Single Inheritance.
    // 3) Overriding is possible.

    // Why put class constants here?
    // 1) The constant value will not be necessary in other class families.
}

/**
 * An incoming / server-side HTTP request class.
 */

class HttpRequest extends Request implements AcceptMediaTypes
{
    // This class can implement groups of constants as necessary.
}

如果您使用的是名称空间,那么代码完成应该是有效的。

PHP手册:接口


这是我对"动态"枚举的看法…所以我可以用变量来调用它,比如从一个窗体。

查看此代码块下的更新版本…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$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);
    }
}

更新:更好的方法…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

}

打电话

1
EnumCategory::${$category};


指出解决方案效果良好。干净光滑。

但是,如果希望使用强类型枚举,则可以使用:

1
2
3
4
5
6
class TestEnum extends Enum
{
    public static $TEST1;
    public static $TEST2;
}
TestEnum::init(); // Automatically initializes enum values

具有如下枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 statement


我根据布莱恩·克林的答案建立了一个图书馆,它的名字叫Greg0ire/Enum。享受!


昨天我在博客上写了这节课。我认为在PHP脚本中很容易使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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;
    }
}

用途:

1
2
3
4
5
class enumWorkType extends Enum
{
        const FULL = 0;
        const SHORT = 1;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// My Enumeration Class
class Enum
{
    protected $m_actions = array();

    public function __construct($actions)
    {
        $this->init($actions);
    }

    public function init($actions)
    {
        $this->m_actions = array();
        for($i = 0; $i < count($actions); ++$i)
        {
            $this->m_actions[$actions[$i]] = ($i + 1);
            define($actions[$i], ($i + 1));
        }
    }

    public function toString($index)
    {
        $keys = array_keys($this->m_actions);
        for($i = 0; $i < count($keys); ++$i)
        {
            if($this->m_actions[$keys[$i]] == $index)
            {
                return $keys[$i];
            }
        }

        return"undefined";
    }

    public function fromString($str)
    {
        return $this->m_actions[$str];
    }
}

// Enumeration creation
$actions = new Enum(array("CREATE","READ","UPDATE","DELETE"));

// Examples
print($action_objects->toString(DELETE));
print($action_objects->fromString("DELETE"));

if($action_objects->fromString($_POST["myAction"]) == CREATE)
{
    print("CREATE");
}

我尝试用php创建一个枚举……这是非常有限的,因为它不支持对象作为枚举值,但仍然有一些用处……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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();
    }
}


我最近开发了一个简单的PHP枚举库:https://github.com/dnl-blkv/simple-php-enum

在写这个答案的时候,它仍然处于发布前的阶段,但是已经完全功能化、文档化并在包装商上发布了。

这可能是一个方便的选择,如果你正在寻找易于实现类似于C/C++的枚举。


不使用反射的更简单、更轻版本:

1
2
3
4
5
6
7
8
9
10
11
abstract class enum {
    private function __construct() {}
    static function has($const) {
        $name = get_called_class();
        return defined("$name::$const");
    }
    static function value($const) {
        $name = get_called_class();
        return defined("$name::$const")? constant("$name::$const") : false;
    }
}

用途:

1
2
3
4
class requestFormat  extends enum { const HTML = 1; const JSON = 2; const XML  = 3; const FORM = 4; }

echo requestFormat::value('JSON'); // 2
echo requestFormat::has('JSON');   // true

这给了常量的优势,也允许检查它们的有效性,但它缺少由更复杂的解决方案提供的其他奇特功能。这个问题越明显,无法检查值的反转(在上面的示例中,您无法检查"2"是否为有效值)。


如果您希望类型安全和一组与该类型匹配的常量,一种方法是为枚举创建一个抽象类,然后用一个锁定的构造函数扩展该类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class DaysOfWeekEnum{
    public function __construct(string $value){
        $this->value = $value;
    }
    public function __toString(){
        return $this->value;
    }

}
class Monday extends DaysOfWeekEnum{
    public function __construct(){
        parent::__construct("Monday");
    }
}

class Tuesday extends DaysOfWeekEnum{
    public function __construct(){
        parent::__construct("Tuesday");
    }
}

然后,您可以让您的方法取daysofweek的一个实例,并将它传递给周一、周二等的一个实例…唯一的缺点是每次你想使用你的枚举时都必须"更新"一个实例,但是我发现它是值得的。

1
2
3
4
5
function printWeekDay(DaysOfWeek $day){
    echo"Today is $day.";
}

printWeekDay(new Monday());

对于简单的枚举,我使用如下结构。通常,您可以将它们用于switch语句。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
  define("OPTION_1","1");
  define("OPTION_2", OPTION_1 + 1);
  define("OPTION_3", OPTION_2 + 1);

  // Some function...
   switch($Val){
    case OPTION_1:{ Perform_1();}break;
    case OPTION_2:{ Perform_2();}break;
    ...
  }
?>

它不像在C++中的原生枚举一样是VulnIET,但是它看起来很有用,并且如果稍后想在其中添加一个选项,则需要较少的维护。