PHP and Enumerations

后端 未结 30 1395
有刺的猬
有刺的猬 2020-11-22 13:39

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 whi

相关标签:
30条回答
  • 2020-11-22 14:10

    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:

    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:

    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
    
    0 讨论(0)
  • 2020-11-22 14:12

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

    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.

    0 讨论(0)
  • 2020-11-22 14:15

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

    Which can be used like this:

    <?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

    0 讨论(0)
  • 2020-11-22 14:16

    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.

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

    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 Enum as 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.


    Hypothetical

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

    (The following is hypothetical; it won't work, 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
    
    0 讨论(0)
  • 2020-11-22 14:16

    It might be as simple as

    enum DaysOfWeek {
        Sunday,
        Monday,
        // ...
    }
    

    in the future.

    PHP RFC: Enumerated Types

    0 讨论(0)
  • 2020-11-22 14:17

    I know this is an old thread, however none of the workarounds I've seen really looked like enums, since almost all workarounds requires you to manually assign values to the enum items, or it requires you to pass an array of enum keys to a function. So I created my own solution for this.

    To create an enum class using my solution one can simply extend this Enum class below, create a bunch of static variables (no need to initialize them), and make a call to yourEnumClass::init() just below the definition of your enum class.

    edit: This only works in php >= 5.3, but it can probably be modified to work in older versions as well

    /**
     * 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;
                    }
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题