PHP和枚举
我知道PHP没有本地枚举。 但是我已经习惯了来自Java世界的他们。 我很乐意使用枚举作为给出IDE自动完成function可以理解的预定义值的方法。
常量可以做到这一点,但是命名空间碰撞问题和(或者实际上是因为 )它们是全局的。 数组没有命名空间问题,但是它们太模糊,在运行时会被覆盖,很less(从不?)知道如何自动填充它们的密钥。
有什么解决scheme/解决方法你通常使用? 有没有人记得PHP的人是否对枚举有任何想法或决定?
根据使用情况,我通常会使用如下简单的东西:
abstract class DaysOfWeek { const Sunday = 0; const Monday = 1; // etc. } $today = DaysOfWeek::Sunday;
但是,其他用例可能需要更多的常量和值的validation。 根据以下有关反思的评论以及其他一些说明 ,下面是一个可以更好地服务于更广泛的案例的扩展示例:
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); } }
通过创build一个扩展BasicEnum的简单的枚举类,您现在可以使用方法来进行简单的inputvalidation:
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
作为一个方面说明,任何时候我至less在一个静态/常量类的数据不会改变 (例如在一个枚举) 上reflection至less一次,我caching这些reflection调用的结果,因为每次使用新的reflection对象最终会有明显的性能影响(存储在多个枚举的联合数组中)。
现在大多数人已经升级到至less5.3,并且SplEnum
可用,这当然也是一个可行的select – 只要你不介意在你的代码库中实际枚举实例化的传统上不直观的概念。 在上面的例子中, BasicEnum
和DaysOfWeek
根本不能被实例化,也不应该被实例化。
什么类常量?
<?php class YourClass { const SOME_CONSTANT = 1; public function echoConstant() { echo self::SOME_CONSTANT; } } echo YourClass::SOME_CONSTANT; $c = new YourClass; $c->echoConstant();
上面的最佳答案是太棒了。 但是,如果以两种不同的方式extend
它,则首先完成的任何扩展将导致对这些函数的调用将创buildcaching。 这个caching将被随后的所有通话使用,不pipe通过哪个分机发起呼叫…
要解决这个问题,请将variables和第一个函数replace为:
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]; }
我使用interface
而不是class
:
interface DaysOfWeek { const Sunday = 0; const Monday = 1; // etc. } var $today = DaysOfWeek::Sunday;
我用常量类:
class Enum { const NAME = 'aaaa'; const SOME_VALUE = 'bbbb'; } print Enum::NAME;
那么,对于像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]; } }
并称之为:
SomeTypeName::toOrdinal("Read"); SomeTypeName::toString(1);
但是我是一个PHP初学者,在语法上苦苦挣扎,所以这可能不是最好的方法。 我尝试了一些类常量,使用reflection从它的值中获取常量名称,可能会更整洁。
我已经在这里评论过其他一些答案,所以我想我也会考虑。 在一天结束的时候,由于PHP不支持枚举types,所以可以采用两种方法之一:破解types枚举,或者以非常难以有效破解的方式生活。
我宁愿忍受这个事实,而是使用其他答案在这里或者另一个方法中使用的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()); } }
枚举示例:
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
作为所有其他枚举扩展的基类允许辅助方法,比如toArray
, isValid
等等。 对我来说,键入枚举( 并pipe理它们的实例 )最终太乱了。
假想
如果存在一个__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
四年后,我又遇到了这个。 我目前的方法是这样的,因为它允许在IDE中完成代码以及types安全:
基类:
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; } }
示例枚举:
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); } }
用法示例:
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');
请注意,相同枚举条目的所有实例都是相同的:
$monday1 = DaysOfWeek::Monday(); $monday2 = DaysOfWeek::Monday(); $monday1 === $monday2; // true
你也可以在switch语句中使用它:
function getGermanWeekDayName(DaysOfWeek $weekDay) { switch ($weekDay) { case DaysOfWeek::Monday(): return 'Montag'; case DaysOfWeek::Tuesday(): return 'Dienstag'; // ... }
您也可以按名称或值创build一个枚举条目:
$monday = DaysOfWeek::fromValue(2); $tuesday = DaysOfWeek::fromName('Tuesday');
或者,您可以从现有的枚举条目中获取名称(即函数名称):
$wednesday = DaysOfWeek::Wednesday() echo $wednesDay->getName(); // Wednesday
如果您需要使用全局唯一的枚举(即使比较不同枚举元素之间的元素)并且易于使用,请随意使用以下代码。 我还添加了一些我觉得有用的方法。 你会在代码最上面的注释中find例子。
<?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 (eg 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 (eg 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"; } } ?>
我也喜欢java中的枚举,为此我用这种方式编写枚举,我认为这是Java中枚举最类似的行为,当然,如果有些想从java中使用更多的方法,应该写在这里,或者在抽象类,但核心思想embedded在下面的代码
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
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>"; }
这里是一个处理php中的types安全的枚举的github库:
这个库处理类生成,类caching,它实现了types安全枚举devise模式,有几个处理枚举的帮助器方法,比如为枚举组合检索一个序号,或者检索一个二进制值。
生成的代码使用一个普通的旧的PHP模板文件,这也是可configuration的,所以你可以提供自己的模板。
这是全面testing覆盖phpunit。
在github上的php-enums(随便叉)
用法:(@see usage.php,或unit testing的更多细节)
<?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
我已经采取了使用下面的方法,因为它使我能够为函数参数提供types安全性,在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");
我在github上find了这个库 ,我认为它提供了一个非常体面的替代scheme。
PHP Enum实现灵感来自SplEnum
- 你可以input-hint:
function setAction(Action $action) {
- 你可以用方法来丰富枚举(例如
format
,parse
,…) - 您可以扩展枚举来添加新的值(使您的枚举
final
以防止它) - 你可以得到所有可能的值列表(见下文)
宣言
<?php use MyCLabs\Enum\Enum; /** * Action enum */ class Action extends Enum { const VIEW = 'view'; const EDIT = 'edit'; }
用法
<?php $action = new Action(Action::VIEW); // or $action = Action::VIEW();
键入提示枚举值:
<?php function setAction(Action $action) { // ... }
这可能是如此简单
enum DaysOfWeek { Sunday, Monday, // ... }
在将来。
PHP RFC:枚举types
我在PHP中看到的最常见的解决scheme是创build一个generics枚举类,然后扩展它。 你可以看看这个 。
更新:或者,我从phpclasses.orgfind这个 。
我知道这是一个古老的线程,但是我所看到的解决方法没有一个看起来像枚举,因为几乎所有的变通办法都要求您手动为枚举项赋值,或者需要将一个枚举键数组传递给function。 所以我为此创build了自己的解决scheme。
要使用我的解决scheme创build一个枚举类,可以简单地在下面扩展这个Enum类,创build一堆静态variables(不需要初始化它们),然后调用enumClass :: init() 。
编辑:这只适用于php> = 5.3,但它可能会被修改为旧版本
/** * 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; } } } } }
The accepted answer is the way to go and is actually what I am doing for simplicity. Most advantages of enumeration are offered (readable, fast, etc.). One concept is missing, however: type safety. In most languages, enumerations are also used to restrict allowed values. Below is an example of how type safety can also be obtained by using private constructors, static instantiation methods and type checking:
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());
We could even go further: using constants in the DaysOfWeek class might lead to misusage: eg one might mistakenly use it this way:
printDayOfWeek(DaysOfWeek::Monday); //triggers a compiler error.
which is wrong (calls integer constant). We can prevent this using private static variables instead of constants:
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; } }
Of course, it is not possible to access integer constants (this was actually the purpose). The intVal method allows to convert a DaysOfWeek object to its integer representation.
Note that we could even go further by implementing a caching mechanism in instantiation methods to save memory in the case enumerations are extensively used…
希望这会有所帮助
Some good solutions on here!
Here's my version.
- It's strongly typed
- It works with IDE auto-completion
- Enums are defined by a code and a description, where the code can be an integer, a binary value, a short string, or basically anything else you want. The pattern could easily be extended to support orther properties.
- It asupports value (==) and reference (===) comparisons and works in switch statements.
I think the main disadvantage is that enum members do have to be separately declared and instantiated, due to the descriptions and PHP's inability to construct objects at static member declaration time. I guess a way round this might be to use reflection with parsed doc comments instead.
The abstract enum looks like this:
<?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; } }
Here's an example concrete enum:
<?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;
And produces this output:
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
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 ) );
Don't use reflection. It makes it extremely difficult to reason about your code and track down where something is being used, and tends to break static analysis tools (eg what's built into your IDE).
This is my take on "dynamic" enum… so that i can call it with variables, ex. from a form.
look at updated verison below this codeblock…
$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); } }
UPDATE: Better way of doing it…
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; }
Call with
EnumCategory::${$category};
Yesterday I wrote this class on my blog . I think it's maybe be easy for use in php scripts:
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; }
Pointed out solution works well. Clean and smooth.
However, if you want strongly typed enumerations, you can use this:
class TestEnum extends Enum { public static $TEST1; public static $TEST2; } TestEnum::init(); // Automatically initializes enum values
With an Enum class looking like:
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; } }
This way, enum values are strongly typed and
TestEnum::$TEST1 === TestEnum::parse('TEST1') // true statement
One of the aspects missing from some of the other answers here is a way to use enums with type hinting.
If you define your enum as a set of constants in an abstract class, eg
abstract class ShirtSize { public const SMALL = 1; public const MEDIUM = 2; public const LARGE = 3; }
then you can't type hint it in a function parameter – for one, because it's not instantiable, but also because the type of ShirtSize::SMALL
is int
, not ShirtSize
.
That's why native enums in PHP would be so much better than anything we can come up with. However, we can approximate an enum by keeping a private property which represents the value of the enum, and then restricting the initialization of this property to our predefined constants. To prevent the enum from being instantiated arbitrarily (without the overhead of type-checking a whitelist), we make the constructor private.
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); } }
Then we can use ShirtSize
like this:
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";
This way, the biggest difference from the user's perspective is that you have to tack on a ()
on the constant's name.
One downside though is that ===
(which compares object equality) will return false when ==
returns true. For that reason, it's best to provide an equals
method, so that users don't have to remember to use ==
and not ===
to compare two enum values.
EDIT: A couple of the existing answers are very similar, particularly: https://stackoverflow.com/a/25526473/2407870 .
I realize this is a very-very-very old thread but I had a thought about this and wanted to know what people thought.
Notes: I was playing around with this and realized that if I just modified the __call()
function that you can get even closer to actual enums
. The __call()
function handles all unknown function calls. So let's say you wan to make three enums
RED_LIGHT, YELLOW_LIGHT, and GREEN_LIGHT. You can do so now by just doing the following:
$c->RED_LIGHT(); $c->YELLOW_LIGHT(); $c->GREEN_LIGHT();
Once defined all you have to do is to call them again to get the values:
echo $c->RED_LIGHT(); echo $c->YELLOW_LIGHT(); echo $c->GREEN_LIGHT();
and you should get 0, 1, and 2. Have fun! This is also now up on GitHub.
Update: I've made it so both the __get()
and __set()
functions are now used. These allow you to not have to call a function unless you want to. Instead, now you can just say:
$c->RED_LIGHT; $c->YELLOW_LIGHT; $c->GREEN_LIGHT;
For both the creation and getting of the values. Because the variables haven't been defined initially, the __get()
function is called (because there isn't a value specified) which sees that the entry in the array hasn't been made. So it makes the entry, assigns it the last value given plus one(+1), increments the last value variable, and returns TRUE. If you set the value:
$c->RED_LIGHT = 85;
Then the __set()
function is called and the last value is then set to the new value plus one (+1). So now we have a fairly good way to do enums and they can be created on the fly.
<?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"; ?>
Stepping on the answer of @Brian Cline I thought I might give my 5 cents
<?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]; } }
I made a library based on Brian Cline's answer, it is named greg0ire/enum Enjoy!
My attempt to create an enum with PHP…it's extremely limited since it doesn't support objects as the enum values but still somewhat useful…
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(); } }
// 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"); }