Editors Note, February 14th 2022: This project is now ABANDONED and no longer supported or updated.

I am proud to announce the addition of an enumeration data type to the asherwunk/phabstractic project.

Enumeration Data Type

An enumeration data type can be summed up by Wikipedia:

In computer programming, an enumerated type (also called enumeration or enum, or factor in the R programming language, and a categorical variable in statistics) is a data type consisting of a set of named values called elementsmembers or enumerators of the type. The enumerator names are usually identifiers that behave as constants in the language. A variable that has been declared as having an enumerated type can be assigned any of the enumerators as a value. In other words, an enumerated type has values that are different from each other, and that can be compared and assigned, but which are not specified by the programmer as having any particular concrete representation in the computer’s memory; compilers and interpreters can represent them arbitrarily.

PHP doesn’t have an enumeration data type, so I built an enumeration generator of my own.  It actually won an award on phpclasses.org!  It has been refactored to use the Configuration feature, formatted to follow the PSR standards as best it can, and relicensed under the MIT License.

Version 3 has made some changes.  Constants can not have their own custom values now, reducing complexity and error prone-ness.  This means that constants are not defined as an associative array! There have also been some changes to the internal enumerations, that being of checking the value against the constants before being set.

Enum Code Generation

The core of the enumeration class is code generated by the private ::createEnum(...) method.  This code uses eval() to execute some definition code to create a class that defines an enumeration.  Of course, in PHP this is really implemented as EnumerationClass::EnumerationValue.  The values assigned to the enumerated values of the enumeration are arbitrary, and generated by the code generator.  The main code generation (baker) in the class lies in ::createEnum(...) method:

private function createEnum($className, array $values)
{
    if (isset($this->namespace) && in_array($this->namespace . '\\' . $className, self::$enums)) {
        throw new Exception\RuntimeException(
            '\\Phabstractic\\Data\\Types\\Enumeration->createEnum: ' .
                $this->namespace . '\\' . $className .
                ' Enumeration Already Defined');
    }
    
    // Start with blank code
    $classCode = '';
    
    /* Place namespace identifier at top of 'code', as eval statements
       operate outside of the namespace context of the code executing 
       the eval statement */
    if (isset($this->namespace) && $this->namespace) {
        $classCode .= 'namespace ' . $this->namespace . ";\n\n";
    }
    
    /* Create the class and its internal variable */
    $classCode .= "class $className implements \Countable {
        private \$value;\n";
    
    /* Version 3.0: Uses reflection to check values against constants */
    $classCode .= "    private \$reflector;\n";
    
    /* This creates the default default value (if no default value 
       is specified) as well as creates the constant definition 
       from the values passed to the function */
    $defaultTemp = 0;
    $counter = 0;
    foreach ($values as $val) {
    //foreach ($values as $identifier => $val) {
        $classCode .= "const $val = $counter;\n\t";
        // !$defaultTemp ? $defaultTemp = $identifier : null;
        $counter++;
    }
    
    /* If the default option has been defined, use that one,
       otherwise Use the default default we defined above */
    if (isset( $this->default ) && $this->default) {
        $classCode .= "\nconst __default = self::" .
            $this->default . ";\n\n\t";
        $classCode .= "\npublic static \$__sdefault = self::" .
            $this->default . ";\n\n\t";
    } else {
        $classCode .= "\nconst __default = 0;\n\n\t";
        $classCode .= "\npublic static \$__sdefault = 0;\n\n\t";
    }
        
    /* Build the rest of the custom object, somewhat self
       explanatory.  There is no 'set' method becuse if you want 
       to get a new enumerator value you would pass that value to a 
       new enumerator instance.  i.e. $aColor = new Color(Color::Red).
    
       The constructor function is the 'set' method then, and 
       checks to make sure the value provided for the new
       enumerator instance is available as a constant in the class 
       through the use of a ReflectionClass
    
       Throws UnexpectedValueException when the constant value is
           not available */
    
    /* version 1.2: added \ qualifier before
       ReflectionClass - April 11th, 2013 */
    $classCode .= "public function __construct(\$initValue = null)
        {\n";
            if ($this->default) {
                $classCode .= "self::\$__sdefault = self::" .
                    $this->default . ";";
            }
    $classCode .= "\n
            \$this->reflector = new \\ReflectionClass(\$this);
            if (\$initValue) {
                if ( \$this->check( \$initValue ) )
                {
                    \$this->value = \$this->reflector->getConstants()[\$initValue];
                } else {
                    throw new \UnexpectedValueException(\"Value not a const in enum $className\");
                }
            } else {
                \$this->value = self::\$__sdefault;
            }
        }
        
        // version 3.0: checks against the constants defined
        protected function check( \$value )
        {
            if ( in_array( \$value, array_keys(\$this->reflector->getConstants()), true ) )
            {
                return true;
            }
            
            return false;
        }
        
        public function __toString()
        {
            return (string) \$this->value;
        }
        
        public function get()
        {
            return \$this->value;
        }
        
        // version 3.0: now checks against the constants defined
        public function set( \$value )
        {
            if ( \$this->check( \$initValue ) )
            {
                \$this->value = \$this->reflector->getConstants()[\$initValue];
            } else {
                throw new \UnexpectedValueException(\"Value not a const in enum $className\");
            }
        }\n";

    // How many enumerator categories are there?
    $classCode .= "public function count() {
        return count(\$this->reflector->getConstants());
    }
    
    static public function getConstants() {
        \$test = new $className();
        \$reflect = new \\ReflectionClass(\$test);
        return \$reflect->getConstants();
    }\n";
    
    // Close up the class definition
    $classCode .= '}';
    
    try {
        eval( $classCode );
    } catch (\Exception $e) {
        throw new Exception\CodeGenerationException(
            '\\Phabstractic\\Data\\Types\\Enumeration->createEnum: ' .
            'Unable to generate ' . $this->className . ' due to internal ' .
            'error.'); // The enumerator couldn't be generated.
    }
    
    self::$enums[] = ( isset($this->namespace) ? $this->namespace : '' ) .
        '\\' . $this->className;
    return true; // The enumerator was generated.
}

In one fell swoop you can create an enumeration by defining the following:

$enumeratedValues = array('RED', 'BLUE', 'GREEN');

$enumeration = new Enum('Colors', $enumeratedValues, array('bake' => true));

It is actually possible to use a static method to create an enumeration as well:

$enumeration = Enum::createEnumerator('Colors', array('RED', 'BLUE', 'GREEN'));

Notice the inclusion of the following code in ::createEnum(...):

if (isset($this->namespace) && in_array($this->namespace . '\\' . $className, self::$enums)) {
    throw new Exception\RuntimeException(
        'Enum->createEnum:' . $this->namespace . '\\' .
            $className . ' Enumeration Already Defined');
}

And

self::$enums[] = ( isset($this->namespace) ? $this->namespace : '' ) .
                '\\' . $this->className;

This ensures that no enumerator is created twice in the same namespace.

An enumeration not only acts as an enumerator data type but can also be instantiated as an enumerated value itself. This is a little clunky, but it seemed the only way to organize it. If you are using an actual enumerator class instantiation then you can compare the values like so:

(string) $instance == EnumClass::EnumValue;

I know that that can get really bizarre if not carefully watched, but that’s the only way I could think of organizing it. You are safer to set a value TO an enumeration:

$value = EnumClass::EnumValue;

But technically, that’s not an enumeration type.

View on GitHub

This is part of the Phabstractic Library.

photo credit: six4x4 via photopin (license)

Asher Wolfstein

Metaverse Resident

About the Author

A metaverse resident, you can find me on Second Life (kadar.talbot) and other online platforms. I write about my digital life, my musings, and my projects as a programmer, webmaster, artist, and game designer. (exist (be wunk) (use rational imagination) (import artist coder maker furry) (conditional (if (eq you asshole) (me (block you))))

View Articles