PHP 和枚举

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/254514/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:05:21  来源:igfitidea点击:

PHP and Enumerations

phpenumeration

提问by Henrik Paul

I know that PHP doesn't have native Enumerations. But I have become accustomed to them from the Java world. I would love to use enums as a way to give predefined values which IDEs' auto-completion features could understand.

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

Constants do the trick, but there's the namespace collision problem and (or actually because) they're global. Arrays don't have the namespace problem, but they're too vague, they can be overwritten at runtime and IDEs rarely (never?) know how to autofill their keys.

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

Are there any solutions/workarounds you commonly use? Does anyone recall whether the PHP guys have had any thoughts or decisions around enums?

是否有您常用的解决方案/变通方法?有没有人记得 PHP 人员是否对枚举有任何想法或决定?

回答by Brian Cline

Depending upon use case, I would normally use something simplelike the following:

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

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

$today = DaysOfWeek::Sunday;

However, other use cases may require more validation of constants and values. Based on the comments below about reflection, and a few other notes, here's an expanded example which may better serve a much wider range of cases:

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

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);
    }
}

By creating a simple enum class that extends BasicEnum, you now have the ability to use methods thusly for simple input validation:

通过创建一个扩展 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

As a side note, any time I use reflection at least once on a static/const class where the data won't change(such as in an enum), I cache the results of those reflection calls, since using fresh reflection objects each time will eventually have a noticeable performance impact (Stored in an assocciative array for multiple enums).

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

Now that most people have finallyupgraded to at least 5.3, and SplEnumis available, that is certainly a viable option as well--as long as you don't mind the traditionally unintuitive notion of having actual enum instantiationsthroughout your codebase. In the above example, BasicEnumand DaysOfWeekcannot be instantiated at all, nor should they be.

现在大多数人终于升级到至少 5.3 并且SplEnum可用,这当然也是一个可行的选择——只要您不介意在整个代码库中具有实际枚举实例的传统上不直观的概念。在上面的例子中,BasicEnumDaysOfWeek根本不能被实例化,也不应该被实例化。

回答by markus

There is a native extension, too. The SplEnum

还有一个本地扩展。该SplEnum

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

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

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

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

Attention:

注意力:

https://www.php.net/manual/en/spl-types.installation.php

https://www.php.net/manual/en/spl-types.installation.php

The PECL extension is not bundled with PHP.

A DLL for this PECL extension is currently unavailable.

PECL 扩展未与 PHP 捆绑在一起。

此 PECL 扩展的 DLL 当前不可用。

回答by Peter Bailey

What about class constants?

类常量呢?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

回答by Neil Townsend

The top answer above is fantastic. However, if you extendit in two different ways, then whichever extension is done first results in a call to the functions will create the cache. This cache will then be used by all subsequent calls, no matter whichever extension the calls are initiated by ...

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

To solve this, replace the variable and first function with:

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

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];
}

回答by andy.gurin

I used classes with constants:

我使用了带有常量的类:

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

print Enum::NAME;

回答by Andi T

I use interfaceinstead of class:

我使用interface代替class

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

var $today = DaysOfWeek::Sunday;

回答by Dan Lugg

I've commented on some of the other answers here, so I figured I would weigh in too. At the end of the day, since PHP doesn't support typed enumerations, you can go one of two ways: hack out typed enumerations, or live with the fact that they're extremely difficult to hack out effectively.

我在这里评论了其他一些答案,所以我想我也会权衡一下。归根结底,由于 PHP 不支持类型化枚举,您可以采用以下两种方式之一:破解类型化枚举,或者接受这样的事实:有效地破解它们极其困难。

I prefer to live with the fact, and instead use the constmethod that other answers here have used in some way or another:

我更愿意接受事实,而是使用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());
    }

}

An example enumeration:

一个示例枚举:

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;

}

Using Enumas a base class from which all other enumerations extend allows for helper methods, such as toArray, isValid, and so on. To me, typed enumerations (and managing their instances) just end up too messy.

使用Enum作为基类,所有其他枚举延伸允许辅助方法,诸如toArrayisValid等。对我来说,类型化枚举(和管理它们的实例)最终会变得太混乱。



Hypothetical

假想

If, there existed a __getStaticmagic method (and preferably an __equalsmagic method too) much of this could be mitigated with a sort of multiton pattern.

如果,存在一种__getStatic魔法方法(最好也是一种__equals魔法方法),其中的大部分都可以通过一种 multiton 模式来缓解。

(The following is hypothetical; it won'twork, though perhaps one day it will)

以下是假设性的;它不会起作用,但也许有一天会起作用

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

回答by aelg

Well, for a simple java like enum in php, I use:

好吧,对于像 php 中的 enum 这样的简单 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];
    }
}

And to call it:

并称之为:

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

But I'm a PHP beginner, struggling with the syntax so this might not be the best way. I experimented some with Class Constants, using Reflection to get the constant name from it's value, might be neater.

但我是一个 PHP 初学者,在语法上苦苦挣扎,所以这可能不是最好的方法。我尝试了一些类常量,使用反射从它的值中获取常量名称,可能会更整洁。

回答by Buck Fixing

Four years later I came across this again. My current approach is this as it allows for code completion in the IDE as well as type safety:

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

Base class:

基类:

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;
    }
}

Example Enum:

示例枚举:

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); }      
}

Example usage:

用法示例:

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');

Note that all instances of the same enum entry are the same:

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

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

You can also use it inside of a switch statement:

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

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

You can also create an enum entry by name or value:

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

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

Or you can just get the name (i.e. the function name) from an existing enum entry:

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

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

回答by Songo

I found this libraryon github and I think it provides a very decent alternative to the answers here.

我在 github 上找到了这个库,我认为它为这里的答案提供了一个非常不错的替代方案。

PHP Enum implementation inspired from SplEnum

受 SplEnum 启发的 PHP 枚举实现

  • You can type-hint: function setAction(Action $action) {
  • You can enrich the enum with methods (e.g. format, parse, …)
  • You can extend the enum to add new values (make your enum finalto prevent it)
  • You can get a list of all the possible values (see below)
  • 您可以键入提示: function setAction(Action $action) {
  • 你可以用丰富方法枚举(例如formatparse,...)
  • 您可以扩展枚举以添加新值(使您的枚举final防止它)
  • 您可以获得所有可能值的列表(见下文)

Declaration

宣言

<?php
use MyCLabs\Enum\Enum;

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

Usage

用法

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

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

type-hint enum values:

类型提示枚举值:

<?php
function setAction(Action $action) {
    // ...
}