我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
当前回答
我知道这是一个非常非常非常古老的话题,但我对此有一个想法,想知道人们是怎么想的。
注:我在玩这个,并意识到如果我只是修改__call()函数,你可以更接近实际的枚举。__call()函数处理所有未知函数调用。假设你想创建三个枚举RED_LIGHT, YELLOW_LIGHT和GREEN_LIGHT。你现在只需做以下事情就可以做到:
$c->RED_LIGHT();
$c->YELLOW_LIGHT();
$c->GREEN_LIGHT();
一旦定义了,你所要做的就是再次调用它们来获取值:
echo $c->RED_LIGHT();
echo $c->YELLOW_LIGHT();
echo $c->GREEN_LIGHT();
得到0 1 2。玩得开心!这个现在也在GitHub上。
更新:我已经这样做了,所以__get()和__set()函数现在都被使用。这允许您不必调用函数,除非您愿意。相反,现在你可以说:
$c->RED_LIGHT;
$c->YELLOW_LIGHT;
$c->GREEN_LIGHT;
对于价值的创造和获取。因为变量最初还没有定义,所以调用__get()函数(因为没有指定值),它会发现数组中的条目还没有创建。因此,它创建条目,将最后一个给定值赋给它+1(+1),对最后一个值变量加1,并返回TRUE。如果你设置这个值:
$c->RED_LIGHT = 85;
然后调用__set()函数,最后一个值被设置为新值+1(+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";
?>
其他回答
最后,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.
}
如果您使用的是名称空间,那么代码补全应该可以工作。
但是,这样做将失去在类族(protected)或单独在类(private)中隐藏常量的能力。根据定义,接口中的所有内容都是公共的。
PHP手册:接口
更新:
PHP 8.1现在有了枚举。
编辑:从PHP 8.1开始,支持枚举:https://www.php.net/manual/en/language.types.enumerations.php
—
根据用例,我通常会使用如下简单的代码:
abstract class DaysOfWeek
{
const Sunday = 0;
const Monday = 1;
// etc.
}
$today = DaysOfWeek::Sunday;
然而,其他用例可能需要对常量和值进行更多的验证。根据下面关于反射的评论和其他一些注意事项,下面是一个扩展的示例,它可能更好地适用于更广泛的情况:
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的简单枚举类,你现在可以使用方法进行简单的输入验证:
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
作为旁注,任何时候我在一个静态/const类上至少使用一次反射,其中数据不会改变(例如在enum中),我都会缓存那些反射调用的结果,因为每次使用新的反射对象最终都会有明显的性能影响(存储在多个enum的关联数组中)。
现在大多数人已经升级到至少5.3版本,而且也有了脾脏,这当然也是一个可行的选择——只要你不介意在你的代码库中使用实际枚举实例化这种传统的不直观的概念。在上面的例子中,BasicEnum和DaysOfWeek根本不能被实例化,也不应该被实例化。
// 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创建一个枚举…这是非常有限的,因为它不支持对象作为枚举值,但仍然有点有用…
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();
}
}
踩在布莱恩·克莱恩的回答上,我想我可能会给出我的5美分
<?php
/**
* A class that simulates Enums behaviour
* <code>
* class Season extends Enum{
* const Spring = 0;
* const Summer = 1;
* const Autumn = 2;
* const Winter = 3;
* }
*
* $currentSeason = new Season(Season::Spring);
* $nextYearSeason = new Season(Season::Spring);
* $winter = new Season(Season::Winter);
* $whatever = new Season(-1); // Throws InvalidArgumentException
* echo $currentSeason.is(Season::Spring); // True
* echo $currentSeason.getName(); // 'Spring'
* echo $currentSeason.is($nextYearSeason); // True
* echo $currentSeason.is(Season::Winter); // False
* echo $currentSeason.is(Season::Spring); // True
* echo $currentSeason.is($winter); // False
* </code>
*
* Class Enum
*
* PHP Version 5.5
*/
abstract class Enum
{
/**
* Will contain all the constants of every enum that gets created to
* avoid expensive ReflectionClass usage
* @var array
*/
private static $_constCacheArray = [];
/**
* The value that separates this instance from the rest of the same class
* @var mixed
*/
private $_value;
/**
* The label of the Enum instance. Will take the string name of the
* constant provided, used for logging and human readable messages
* @var string
*/
private $_name;
/**
* Creates an enum instance, while makes sure that the value given to the
* enum is a valid one
*
* @param mixed $value The value of the current
*
* @throws \InvalidArgumentException
*/
public final function __construct($value)
{
$constants = self::_getConstants();
if (count($constants) !== count(array_unique($constants))) {
throw new \InvalidArgumentException('Enums cannot contain duplicate constant values');
}
if ($name = array_search($value, $constants)) {
$this->_value = $value;
$this->_name = $name;
} else {
throw new \InvalidArgumentException('Invalid enum value provided');
}
}
/**
* Returns the constant name of the current enum instance
*
* @return string
*/
public function getName()
{
return $this->_name;
}
/**
* Returns the value of the current enum instance
*
* @return mixed
*/
public function getValue()
{
return $this->_value;
}
/**
* Checks whether this enum instance matches with the provided one.
* This function should be used to compare Enums at all times instead
* of an identity comparison
* <code>
* // Assuming EnumObject and EnumObject2 both extend the Enum class
* // and constants with such values are defined
* $var = new EnumObject('test');
* $var2 = new EnumObject('test');
* $var3 = new EnumObject2('test');
* $var4 = new EnumObject2('test2');
* echo $var->is($var2); // true
* echo $var->is('test'); // true
* echo $var->is($var3); // false
* echo $var3->is($var4); // false
* </code>
*
* @param mixed|Enum $enum The value we are comparing this enum object against
* If the value is instance of the Enum class makes
* sure they are instances of the same class as well,
* otherwise just ensures they have the same value
*
* @return bool
*/
public final function is($enum)
{
// If we are comparing enums, just make
// sure they have the same toString value
if (is_subclass_of($enum, __CLASS__)) {
return get_class($this) === get_class($enum)
&& $this->getValue() === $enum->getValue();
} else {
// Otherwise assume $enum is the value we are comparing against
// and do an exact comparison
return $this->getValue() === $enum;
}
}
/**
* Returns the constants that are set for the current Enum instance
*
* @return array
*/
private static function _getConstants()
{
if (self::$_constCacheArray == null) {
self::$_constCacheArray = [];
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$_constCacheArray)) {
$reflect = new \ReflectionClass($calledClass);
self::$_constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$_constCacheArray[$calledClass];
}
}