PHP和枚举

Hen*_*aul 1114 php enumeration

我知道PHP没有本机枚举.但我已经从Java世界习惯了它们.我希望使用枚举作为一种方式来提供IDE的自动完成功能可以理解的预定义值.

常量可以解决问题,但是存在名称空间冲突问题,并且(或实际上因为)它们是全局的.数组没有命名空间问题,但是它们太模糊了,它们可以在运行时覆盖,IDE很少(从不?)知道如何自动填充其键.

您是否经常使用任何解决方案/解决方法?有谁回忆一下PHP家伙是否对枚举有任何想法或决定?

Bri*_*ine 1458

根据用例,我通常会使用如下简单的东西:

abstract class DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

$today = DaysOfWeek::Sunday;
Run Code Online (Sandbox Code Playgroud)

但是,其他用例可能需要更多的常量和值验证.根据以下关于反思的评论以及其他一些注释,这里有一个扩展的例子,可以更好地服务于更广泛的案例:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

通过创建一个扩展BasicEnum的简单枚举类,您现在可以使用方法进行简单的输入验证:

abstract class DaysOfWeek extends BasicEnum {
    const Sunday = 0;
    const Monday = 1;
    const Tuesday = 2;
    const Wednesday = 3;
    const Thursday = 4;
    const Friday = 5;
    const Saturday = 6;
}

DaysOfWeek::isValidName('Humpday');                  // false
DaysOfWeek::isValidName('Monday');                   // true
DaysOfWeek::isValidName('monday');                   // true
DaysOfWeek::isValidName('monday', $strict = true);   // false
DaysOfWeek::isValidName(0);                          // false

DaysOfWeek::isValidValue(0);                         // true
DaysOfWeek::isValidValue(5);                         // true
DaysOfWeek::isValidValue(7);                         // false
DaysOfWeek::isValidValue('Friday');                  // false
Run Code Online (Sandbox Code Playgroud)

作为旁注,任何时候我在静态/ const类上使用反射至少一次数据不会改变(例如在枚举中),我会缓存那些反射调用的结果,因为每次使用新的反射对象最终会产生明显的性能影响(存储在多个枚举的关联数组中).

现在大多数人已经最终升级到至少5.3,并且SplEnum可用,这当然是一个可行的选择 - 只要你不介意在整个代码库中实际实现枚举实例的传统不直观的概念.在上述例子中,BasicEnumDaysOfWeek不能在所有的实例化,也不应.

  • 我也用它.您可能还会考虑将类设为`abstract`和`final`,因此无法实例化或扩展. (70认同)
  • 关于抽象或最终; 我让它们成为最终的并给它们一个空的私有构造函数 (45认同)
  • 你可以把一个类叫做'abstract`和`final`?我知道在Java中这是不允许的.你可以在PHP中做到这一点? (20认同)
  • @ryeguy看起来你不能把它作为*`abstract`和`final`.在那种情况下,我会去抽象. (20认同)
  • 小心使用0,所以你不要遇到任何意想不到的错误比较问题,例如在`switch`语句中与`null`和朋友等价.到过那里. (20认同)
  • 如果`BasicEnum`在同一个应用程序中被多个类扩展,那么这个奇妙的代码中会出现一个非常小的错误,这会导致一些奇怪的结果.我将在下面的答案中进行修正. (5认同)
  • 迭代运行时验证的名称和值:`$ r = new ReflectionClass('DaysOfWeek'); foreach($ r-> getConstants()as $ day => $ code){...}` (4认同)
  • 正如@NeilTownsend所提到的,这段代码有一个严重的错误.这是一个演示.http://codepad.viper-7.com/boTjXT只在​​php做一个绝对致命的bug的答案收到700+ upvotes大声笑. (3认同)
  • 您可以使用命名空间而不是PHP 5.3中的类来执行此操作吗? (2认同)
  • 是的,既然命名空间可用,您也可以选择周日/周一等.`DaysOfWeek`命名空间中的常量,将它们称为"DaysOfWeek\Sunday"等等.两种方法之间的选择最终将归结为个人在设计和符号方面的偏好,但在5.3及更高版本中两者同样有效. (2认同)

mar*_*kus 181

还有一个原生扩展.该SplEnum

SplEnum提供了在PHP中本地模拟和创建枚举对象的功能.

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

  • @markus,〜native,因为它需要安装自定义扩展 (6认同)
  • 小心使用它.SPL类型是实验性的:_"此扩展是实验性的.此扩展的行为,包括其功能的名称以及围绕此扩展的任何其他文档,可能会在未来的PHP版本中发生更改,恕不另行通知.此扩展应使用,风险自负."_ (6认同)
  • ***SplEnum***不与PHP捆绑,需要[SPL_Types extention](http://php.net/manual/en/spl-types.installation.php) (6认同)
  • 我又回来了.我不希望你们编辑链接. (5认同)
  • 以下是使用splenum的示例:http://www.dreamincode.net/forums/topic/201638-enum-in-php/ (4认同)
  • 我回头了,当我看到链接时,我更喜欢它.它给了我上下文信息. (4认同)
  • 整个 SPL_Types 以及 SplEnum 都已从 php.net 中消失:https://www.php.net/manual/en/book.spl-types.php。并且不再维护包 https://pecl.php.net/package/SPL_Types 。RIP SPL 类型。 (2认同)

Pet*_*ley 45

类常量怎么样?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();
Run Code Online (Sandbox Code Playgroud)

  • `echoConstant` 可以替换为 `__toString`。然后简单地“echo $c” (3认同)

Nei*_*end 34

上面的最佳答案是太棒了.但是,如果您extend以两种不同的方式使用它,那么无论哪个扩展都先完成,导致对函数的调用将创建缓存.然后,所有后续呼叫都将使用此缓存,无论呼叫由哪个分机发起......

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

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];
}
Run Code Online (Sandbox Code Playgroud)

  • 有这个问题。Brian或具有编辑权限的人应在接受的答案中对其进行触摸。我在代码中使用getConstants()函数中的'static ::'方法而不是'self ::'并在子枚举中重新声明了$ constCache,从而解决了该问题。 (2认同)

And*_*i T 28

我使用interface而不是class:

interface DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

var $today = DaysOfWeek::Sunday;
Run Code Online (Sandbox Code Playgroud)

  • `class Foo实现了DaysOfWeek {}`然后`Foo :: Sunday` ......什么? (6认同)
  • 为了防止实例? (5认同)
  • 接口用于强制类实现完整性,这超出了接口的范围 (4认同)
  • 问题的作者要求解决两件事:IDE的命名空间和自动完成.作为评价最高的答案,最简单的方法是使用`class`(或`interface`,这只是一个偏好问题). (3认同)
  • @ user3886650 Java中可以使用接口来保持常量值.因此,您不必强制实例化类以获取常量值,并且任何IDE都会在其上提供代码完成.此外,如果您创建一个实现该接口的类,它将继承所有这些常量 - 有时非常方便. (2认同)

yiv*_*ivi 27

从 PHP 8.1 开始,您可以使用本机枚举

基本语法如下所示:

enum TransportMode {
  case Bicycle;
  case Car;
  case Ship;
  case Plane;
  case Feet;
}
Run Code Online (Sandbox Code Playgroud)
function travelCost(Vehicle $vehicle, 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);
Run Code Online (Sandbox Code Playgroud)

价值观

默认情况下,枚举不受任何类型的标量支持。所以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
Run Code Online (Sandbox Code Playgroud)

支持枚举

您还可以拥有“支持”的枚举,其中每个枚举案例都由 anint或 a “支持” string

enum Metal: int {
  case Gold = 1932;
  case Silver = 1049;
  case Lead = 1134;
  case Uranium = 1905;
  case Copper = 894;
}
Run Code Online (Sandbox Code Playgroud)
  • 如果一个案例有一个支持值,所有案例都需要一个支持值,没有自动生成的值。
  • 请注意,支持值的类型是在枚举名称之后声明的
  • 支持的值是只读的
  • 标量值必须是唯一的
  • 值必须是文字或文字表达式
  • 要读取支持的值,您可以访问该value属性:Metal::Gold->value

最后,支持的枚举在BackedEnum内部实现了一个接口,它公开了两个方法:

  • from(int|string): self
  • tryFrom(int|string): ?self

它们几乎是等价的,但是如果找不到值,第一个将抛出异常,第二个将简单地 return 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
Run Code Online (Sandbox Code Playgroud)

方法

枚举可能有方法,从而实现接口。

// 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
Run Code Online (Sandbox Code Playgroud)

价值清单

Pure Enums 和 Backed Enums 都在内部实现了接口UnitEnum,其中包括(静态)方法UnitEnum::cases(),并允许检索枚举中定义的案例数组:

$modes = TransportMode::cases();
Run Code Online (Sandbox Code Playgroud)

现在$modes是:

[
    TransportMode::Bicycle,
    TransportMode::Car,
    TransportMode::Ship,
    TransportMode::Plane
    TransportMode::Feet
]
Run Code Online (Sandbox Code Playgroud)

静态方法

枚举可以实现自己的static方法,这些方法通常用于专门的构造函数。


这涵盖了基础知识。要获得全部信息,请访问相关的 RFC,直到该功能在 PHP 文档中发布并发布。

  • 一点补充。对于这个出色的答案。因为 `::cases()` 返回一个可以使用 `foreach` 的可迭代类型。例如`foreach( TransportMode::cases() as $mode ) { echo $mode-&gt;value; }` (3认同)
  • 2022 年,该答案应标记为“答案”。所有其他答案似乎都是反模式,没有正确使用语言功能。 (2认同)

小智 26

我使用了常量类:

class Enum {
    const NAME       = 'aaaa';
    const SOME_VALUE = 'bbbb';
}

print Enum::NAME;
Run Code Online (Sandbox Code Playgroud)

  • 我们不希望user/developper能够更改Enum的值.这就是为什么你必须先放一个const (2认同)

Dan*_*ugg 25

我在这里评论了其他一些答案,所以我想我也会权衡.在一天结束时,由于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());
    }

}
Run Code Online (Sandbox Code Playgroud)

枚举示例:

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;

}
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)


小智 23

好吧,对于像php这样的简单java,我使用:

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];
    }
}
Run Code Online (Sandbox Code Playgroud)

并称之为:

SomeTypeName::toOrdinal("Read");
SomeTypeName::toString(1);
Run Code Online (Sandbox Code Playgroud)

但我是一个PHP初学者,在语法方面苦苦挣扎,所以这可能不是最好的方法.我用类常量实验了一些,使用Reflection从它的值中获取常量名称,可能更整洁.

  • IDE中没有自动完成功能,因此会刺激猜测工作。常量将启用自动完成功能,听起来更好。 (2认同)

Buc*_*ing 17

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

基类:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

示例枚举:

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); }      
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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');
Run Code Online (Sandbox Code Playgroud)

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

$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true
Run Code Online (Sandbox Code Playgroud)

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

function getGermanWeekDayName(DaysOfWeek $weekDay)
{
    switch ($weekDay)
    {
        case DaysOfWeek::Monday(): return 'Montag';
        case DaysOfWeek::Tuesday(): return 'Dienstag';
        // ...
}
Run Code Online (Sandbox Code Playgroud)

您还可以按名称或值创建枚举条目:

$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');
Run Code Online (Sandbox Code Playgroud)

或者您可以从现有的枚举条目中获取名称(即函数名称):

$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday
Run Code Online (Sandbox Code Playgroud)


Chr*_*Fox 7

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

<?php

/**
 * Class Enum
 * 
 * @author Christopher Fox <christopher.fox@gmx.de>
 *
 * @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".
 * 
 * <code>
 * Enum::Create('UserState', 'inactive', 'active', 'banned', 'deleted');
 * </code>
 *
 * Using Enums:
 * ============
 *
 * The following example demonstrates how to compare two Enum elements
 *
 * <code>
 * var_dump(UserState::inactive == UserState::banned); // result: false
 * var_dump(UserState::active == UserState::active); // result: true
 * </code>
 *
 * Special Enum methods:
 * =====================
 *
 * Get the number of elements in an Enum:
 *
 * <code>
 * echo UserState::CountEntries(); // result: 4
 * </code>
 *
 * Get a list with all elements of the Enum:
 *
 * <code>
 * $allUserStates = UserState::GetEntries();
 * </code>
 *
 * Get a name of an element:
 *
 * <code>
 * echo UserState::GetName(UserState::deleted); // result: deleted
 * </code>
 *
 * 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.
 *
 * <code>
 * echo UserState::GetDatabaseID(UserState::active); // result: 2
 * </code>
 */
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 . " {\n"
                        . "private static \$name = '" . $name . "';\n"
                        . $this->getClassConstants($name, $fields)
                        . $this->getFunctionGetEntries($name)
                        . $this->getFunctionCountEntries($name)
                        . $this->getFunctionGetDatabaseID()
                        . $this->getFunctionGetName()
                        . "}";

            // Create the class for this enumeration
            eval($classDeclaration);
        }
    }

    /**
     * Returns the code of the class constants
     * for an enumeration. These are the representations
     * of the elements.
     * 
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     *
     * @return string The code of the class constants
     */
    private function getClassConstants($name, $fields)
    {
        $constants = '';

        foreach ($fields as $field)
        {
            // Create a unique ID for the Enum element
            // This ID is unique because class and variables
            // names can't contain a semicolon. Therefore we
            // can use the semicolon as a separator here.
            $uniqueID = $name . ";" . $field;
            $constants .=   "const " . $field . " = '". $uniqueID . "';\n";
            // Store the unique ID
            array_push($this->enums[$name], $uniqueID);
        }

        return $constants;
    }

    /**
     * Returns the code of the function "GetEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "GetEntries()"
     */
    private function getFunctionGetEntries($name) 
    {
        $entryList = '';        

        // Put the unique element IDs in single quotes and
        // separate them with commas
        foreach ($this->enums[$name] as $key => $entry)
        {
            if ($key > 0) $entryList .= ',';
            $entryList .= "'" . $entry . "'";
        }

        return  "public static function GetEntries() { \n"
            . " return array(" . $entryList . ");\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "CountEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "CountEntries()"
     */
    private function getFunctionCountEntries($name) 
    {
        // This function will simply return a constant number (e.g. return 5;)
        return  "public static function CountEntries() { \n"
            . " return " . count($this->enums[$name]) . ";\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetDatabaseID()"
     * for an enumeration
     * 
     * @return string The code of the function "GetDatabaseID()"
     */
    private function getFunctionGetDatabaseID()
    {
        // Check for the index of this element inside of the array
        // of elements and add +1
        return  "public static function GetDatabaseID(\$entry) { \n"
            . "\$key = array_search(\$entry, self::GetEntries());\n"
            . " return \$key + 1;\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetName()"
     * for an enumeration
     *
     * @return string The code of the function "GetName()"
     */
    private function getFunctionGetName()
    {
        // Remove the class name from the unique ID 
        // and return this value (which is the element name)
        return  "public static function GetName(\$entry) { \n"
            . "return substr(\$entry, strlen(self::\$name) + 1 , strlen(\$entry));\n"
            . "}\n";
    }

}


?>
Run Code Online (Sandbox Code Playgroud)

  • 使用 `eval()` 只是为了声明新的 Enums 运行时?哎呀。我没感觉 在定义正确的 Enum 类之前,如何防止其他类创建不正确的 Enum 类?在运行之前不知道枚举吗?正如@corsiKa 暗示的那样,没有 IDE 自动完成功能。我看到的唯一好处是懒惰的编码。 (2认同)

小智 7

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


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

Run Code Online (Sandbox Code Playgroud)

  • 我做了几乎相同的事情,虽然有两个小的补充:我隐藏了静态getter背后的静态值.一个原因是,我在视觉上更喜欢`FruitsEnum :: Apple()`而不是FruitsEnum :: $ Apple`,但更重要的原因是防止其他人设置`$ APPLE`,从而打破了整个应用程序的枚举.另一个是一个简单的私有静态标志`$ initialized`,它确保在第一次调用它之后调用`init()`变为no-op(所以没有人可以搞乱它). (3认同)

Son*_*ngo 7

我在github上找到了这个库,我认为它提供了一个非常好的替代方案.

PHP Enum实现受到SplEnum的启发

  • 你可以输入提示: function setAction(Action $action) {
  • 你可以用丰富方法枚举(例如format,parse,...)
  • 您可以扩展枚举以添加新值(使您的枚举final可以防止它)
  • 您可以获得所有可能值的列表(参见下文)

宣言

<?php
use MyCLabs\Enum\Enum;

/**
 * Action enum
 */
class Action extends Enum
{
    const VIEW = 'view';
    const EDIT = 'edit';
}
Run Code Online (Sandbox Code Playgroud)

用法

<?php
$action = new Action(Action::VIEW);

// or
$action = Action::VIEW();
Run Code Online (Sandbox Code Playgroud)

type-hint枚举值:

<?php
function setAction(Action $action) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)


Ant*_*dge 7

最后,PHP 7.1+ 的答案包含无法覆盖的常量。

/**
 * An interface that groups HTTP Accept: header Media Types in one place.
 */
interface MediaTypes
{
    /**
    * 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 MediaTypes
{
    // This class can implement groups of constants as necessary.
}
Run Code Online (Sandbox Code Playgroud)

如果您使用命名空间,代码完成应该可以工作。

protected然而,这样做时,您将失去隐藏类族 ( ) 或单独的类 ( )中的常量的能力private。根据定义, an 中的所有内容Interface都是public

PHP 手册:接口

更新:

PHP 8.1 现在有枚举


jgl*_*tre 6

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<br>";
}
Run Code Online (Sandbox Code Playgroud)


Tor*_*rge 6

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

定义:

class Fruit extends Enum {
    static public $APPLE = 1;
    static public $ORANGE = 2;
}
Fruit::initialize(); //Can also be called in autoloader
Run Code Online (Sandbox Code Playgroud)

切换枚举

$myFruit = Fruit::$APPLE;

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

>> I like apples
Run Code Online (Sandbox Code Playgroud)

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

/** Function only accepts Fruit enums as input**/
function echoFruit(Fruit $fruit) {
    echo $fruit->getName().": ".$fruit->getValue()."\n";
}

/** 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
Run Code Online (Sandbox Code Playgroud)

将枚举作为字符串回显

echo "I have an $myFruit\n";

>> I have an APPLE
Run Code Online (Sandbox Code Playgroud)

通过整数获取 Enum

$myFruit = Fruit::getByValue(2);

echo "Now I have an $myFruit\n";

>> Now I have an ORANGE
Run Code Online (Sandbox Code Playgroud)

按名称获取枚举

$myFruit = Fruit::getByName("APPLE");

echo "But I definitely prefer an $myFruit\n\n";

>> But I definitely prefer an APPLE
Run Code Online (Sandbox Code Playgroud)

枚举类:

/**
 * @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;
    }

}
Run Code Online (Sandbox Code Playgroud)

添加

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

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();
Run Code Online (Sandbox Code Playgroud)


Noa*_*ich 5

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

更新:或者,我从phpclasses.org 找到了这个.


小智 5

这是一个用于处理php中类型安全枚举的github库:

这个库处理类生成,类缓存,它实现了Type Safe Enumeration设计模式,有几个辅助方法来处理枚举,比如为枚举组合检索枚举排序或检索二进制值的序数.

生成的代码使用一个普通的旧php模板文件,该文件也是可配置的,因此您可以提供自己的模板.

它是用phpunit覆盖的完整测试.

github上的php-enums(随意分叉)

用法:(@ see usage.php,或单元测试了解更多详情)

<?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\name\space', true);

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

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

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

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

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

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

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

echo "->getBinary()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getBinary() . "\n";
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)


Bri*_*her 5

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

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");
Run Code Online (Sandbox Code Playgroud)


Mar*_*ing 5

我意识到这是一个非常非常非常古老的话题,但我对此有过思考,并且想知道人们的想法。

注意:我正在研究这个,并意识到如果我只是修改该__call()函数,您可以更接近实际的enums. 该__call()函数处理所有未知的函数调用。假设您想要制作三个enums红光、黄光和绿光。您现在只需执行以下操作即可做到这一点:

$c->RED_LIGHT();
$c->YELLOW_LIGHT();
$c->GREEN_LIGHT();
Run Code Online (Sandbox Code Playgroud)

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

echo $c->RED_LIGHT();
echo $c->YELLOW_LIGHT();
echo $c->GREEN_LIGHT();
Run Code Online (Sandbox Code Playgroud)

你应该得到 0、1 和 2。玩得开心!该内容现在也已发布在 GitHub 上。

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

$c->RED_LIGHT;
$c->YELLOW_LIGHT;
$c->GREEN_LIGHT;
Run Code Online (Sandbox Code Playgroud)

对于价值的创造和获取。由于变量最初尚未定义,因此__get()调用该函数(因为未指定值),该函数会发现数组中的条目尚未创建。因此,它创建条目,为其分配给定的最后一个值加一 (+1),递增最后一个值变量,然后返回 TRUE。如果您设置该值:

$c->RED_LIGHT = 85;
Run Code Online (Sandbox Code Playgroud)

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

<?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
#           sim_sales@paypal.com.  :-)
#
#   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 . "\n";
#   echo $c->YELLOW_LIGHT . "\n";
#   echo $c->GREEN_LIGHT . "\n";

?>
Run Code Online (Sandbox Code Playgroud)


myk*_*hal 5

它可能很简单

enum DaysOfWeek {
    Sunday,
    Monday,
    // ...
}
Run Code Online (Sandbox Code Playgroud)

在将来.

PHP RFC:枚举类型