PHP and Enumerations

后端 未结 30 1392
有刺的猬
有刺的猬 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 13:56

    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];
        }
    }
    
    0 讨论(0)
  • 2020-11-22 13:57

    Here is a github library for handling type-safe enumerations in php:

    This library handle classes generation, classes caching and it implements the Type Safe Enumeration design pattern, with several helper methods for dealing with enums, like retrieving an ordinal for enums sorting, or retrieving a binary value, for enums combinations.

    The generated code use a plain old php template file, which is also configurable, so you can provide your own template.

    It is full test covered with phpunit.

    php-enums on github (feel free to fork)

    Usage: (@see usage.php, or unit tests for more details)

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

    Output:

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

    My Enum class definition below is Strongly typed, and very natural to use and define.

    Definition:

    class Fruit extends Enum {
        static public $APPLE = 1;
        static public $ORANGE = 2;
    }
    Fruit::initialize(); //Can also be called in autoloader
    

    Switch over Enum

    $myFruit = Fruit::$APPLE;
    
    switch ($myFruit) {
        case Fruit::$APPLE  : echo "I like apples\n";  break;
        case Fruit::$ORANGE : echo "I hate oranges\n"; break;
    }
    
    >> I like apples
    

    Pass Enum as parameter (Strongly typed)

    /** Function only accepts Fruit enums as input**/
    function echoFruit(Fruit $fruit) {
        echo $fruit->getName().": ".$fruit->getValue()."\n";
    }
    
    /** Call function with each Enum value that Fruit has */
    foreach (Fruit::getList() as $fruit) {
        echoFruit($fruit);
    }
    
    //Call function with Apple enum
    echoFruit(Fruit::$APPLE)
    
    //Will produce an error. This solution is strongly typed
    echoFruit(2);
    
    >> APPLE: 1
    >> ORANGE: 2
    >> APPLE: 1
    >> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given
    

    Echo Enum as string

    echo "I have an $myFruit\n";
    
    >> I have an APPLE
    

    Get Enum by integer

    $myFruit = Fruit::getByValue(2);
    
    echo "Now I have an $myFruit\n";
    
    >> Now I have an ORANGE
    

    Get Enum by Name

    $myFruit = Fruit::getByName("APPLE");
    
    echo "But I definitely prefer an $myFruit\n\n";
    
    >> But I definitely prefer an APPLE
    

    The Enum Class:

    /**
     * @author Torge Kummerow
     */
    class Enum {
    
        /**
         * Holds the values for each type of Enum
         */
        static private $list = array();
    
        /**
         * Initializes the enum values by replacing the number with an instance of itself
         * using reflection
         */
        static public function initialize() {
            $className = get_called_class();
            $class = new ReflectionClass($className);
            $staticProperties = $class->getStaticProperties();
    
            self::$list[$className] = array();
    
            foreach ($staticProperties as $propertyName => &$value) {
                if ($propertyName == 'list')
                    continue;
    
                $enum = new $className($propertyName, $value);
                $class->setStaticPropertyValue($propertyName, $enum);
                self::$list[$className][$propertyName] = $enum;
            } unset($value);
        }
    
    
        /**
         * Gets the enum for the given value
         *
         * @param integer $value
         * @throws Exception
         *
         * @return Enum
         */
        static public function getByValue($value) {
            $className = get_called_class();
            foreach (self::$list[$className] as $propertyName=>&$enum) {
                /* @var $enum Enum */
                if ($enum->value == $value)
                    return $enum;
            } unset($enum);
    
            throw new Exception("No such enum with value=$value of type ".get_called_class());
        }
    
        /**
         * Gets the enum for the given name
         *
         * @param string $name
         * @throws Exception
         *
         * @return Enum
         */
        static public function getByName($name) {
            $className = get_called_class();
            if (array_key_exists($name, static::$list[$className]))
                return self::$list[$className][$name];
    
            throw new Exception("No such enum ".get_called_class()."::\$$name");
        }
    
    
        /**
         * Returns the list of all enum variants
         * @return Array of Enum
         */
        static public function getList() {
            $className = get_called_class();
            return self::$list[$className];
        }
    
    
        private $name;
        private $value;
    
        public function __construct($name, $value) {
            $this->name = $name;
            $this->value = $value;
        }
    
        public function __toString() {
            return $this->name;
        }
    
        public function getValue() {
            return $this->value;
        }
    
        public function getName() {
            return $this->name;
        }
    
    }
    

    Addition

    You can ofcourse also add comments for IDEs

    class Fruit extends Enum {
    
        /**
         * This comment is for autocomplete support in common IDEs
         * @var Fruit A yummy apple
         */
        static public $APPLE = 1;
    
        /**
         * This comment is for autocomplete support in common IDEs
         * @var Fruit A sour orange
         */
        static public $ORANGE = 2;
    }
    
    //This can also go to the autoloader if available.
    Fruit::initialize();
    
    0 讨论(0)
  • 2020-11-22 13:58
    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).

    0 讨论(0)
  • 2020-11-22 13:59

    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 want 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";
    
    ?>
    
    0 讨论(0)
  • 2020-11-22 13:59

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