我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
当前回答
上面的答案太棒了。但是,如果以两种不同的方式进行扩展,那么无论先进行哪种扩展,都会导致对函数的调用,从而创建缓存。这个缓存将被所有后续调用使用,无论调用是由哪个扩展发起的…
要解决这个问题,将变量和第一个函数替换为:
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];
}
其他回答
下面是一个github库,用于在php中处理类型安全的枚举:
这个库处理类生成、类缓存,并实现了Type Safe Enumeration设计模式,使用几个辅助方法来处理枚举,比如为枚举排序检索序号,或为枚举组合检索二进制值。
生成的代码使用一个普通的旧php模板文件,该文件也是可配置的,因此您可以提供自己的模板。
它是由phpunit覆盖的完整测试。
Php-enums在github (feel free to fork)
用法:(@参见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";
}
输出:
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
我知道这是一个非常非常非常古老的话题,但我对此有一个想法,想知道人们是怎么想的。
注:我在玩这个,并意识到如果我只是修改__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";
?>
这里有一些很好的解决方案!
这是我的版本。
它是强类型的 它与IDE自动补全一起工作 枚举由代码和描述定义,其中代码可以是整数、二进制值、短字符串或基本上任何您想要的内容。可以很容易地扩展该模式以支持其他属性。 它支持值(==)和引用(===)比较,并在switch语句中工作。
我认为主要的缺点是枚举成员必须分别声明和实例化,这是由于描述和PHP不能在静态成员声明时构造对象。我想绕过这个问题的一种方法可能是使用带有解析过的文档注释的反射。
抽象枚举看起来像这样:
<?php
abstract class AbstractEnum
{
/** @var array cache of all enum instances by class name and integer value */
private static $allEnumMembers = array();
/** @var mixed */
private $code;
/** @var string */
private $description;
/**
* Return an enum instance of the concrete type on which this static method is called, assuming an instance
* exists for the passed in value. Otherwise an exception is thrown.
*
* @param $code
* @return AbstractEnum
* @throws Exception
*/
public static function getByCode($code)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
return $concreteMembers[$code];
}
throw new Exception("Value '$code' does not exist for enum '".get_called_class()."'");
}
public static function getAllMembers()
{
return self::getConcreteMembers();
}
/**
* Create, cache and return an instance of the concrete enum type for the supplied primitive value.
*
* @param mixed $code code to uniquely identify this enum
* @param string $description
* @throws Exception
* @return AbstractEnum
*/
protected static function enum($code, $description)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
throw new Exception("Value '$code' has already been added to enum '".get_called_class()."'");
}
$concreteMembers[$code] = $concreteEnumInstance = new static($code, $description);
return $concreteEnumInstance;
}
/**
* @return AbstractEnum[]
*/
private static function &getConcreteMembers() {
$thisClassName = get_called_class();
if (!array_key_exists($thisClassName, self::$allEnumMembers)) {
$concreteMembers = array();
self::$allEnumMembers[$thisClassName] = $concreteMembers;
}
return self::$allEnumMembers[$thisClassName];
}
private function __construct($code, $description)
{
$this->code = $code;
$this->description = $description;
}
public function getCode()
{
return $this->code;
}
public function getDescription()
{
return $this->description;
}
}
下面是一个具体枚举示例:
<?php
require('AbstractEnum.php');
class EMyEnum extends AbstractEnum
{
/** @var EMyEnum */
public static $MY_FIRST_VALUE;
/** @var EMyEnum */
public static $MY_SECOND_VALUE;
/** @var EMyEnum */
public static $MY_THIRD_VALUE;
public static function _init()
{
self::$MY_FIRST_VALUE = self::enum(1, 'My first value');
self::$MY_SECOND_VALUE = self::enum(2, 'My second value');
self::$MY_THIRD_VALUE = self::enum(3, 'My third value');
}
}
EMyEnum::_init();
可以这样使用:
<?php
require('EMyEnum.php');
echo EMyEnum::$MY_FIRST_VALUE->getCode().' : '.EMyEnum::$MY_FIRST_VALUE->getDescription().PHP_EOL.PHP_EOL;
var_dump(EMyEnum::getAllMembers());
echo PHP_EOL.EMyEnum::getByCode(2)->getDescription().PHP_EOL;
并产生如下输出:
1 : My first value array(3) { [1]=> object(EMyEnum)#1 (2) { ["code":"AbstractEnum":private]=> int(1) ["description":"AbstractEnum":private]=> string(14) "My first value" } [2]=> object(EMyEnum)#2 (2) { ["code":"AbstractEnum":private]=> int(2) ["description":"AbstractEnum":private]=> string(15) "My second value" } [3]=> object(EMyEnum)#3 (2) { ["code":"AbstractEnum":private]=> int(3) ["description":"AbstractEnum":private]=> string(14) "My third value" } } My second value
类常量呢?
<?php
class YourClass
{
const SOME_CONSTANT = 1;
public function echoConstant()
{
echo self::SOME_CONSTANT;
}
}
echo YourClass::SOME_CONSTANT;
$c = new YourClass;
$c->echoConstant();
昨天我在博客上写了这门课。我认为在php脚本中使用它可能很容易:
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;
}
}
用法:
class enumWorkType extends Enum
{
const FULL = 0;
const SHORT = 1;
}