
Primus2: New Data Type (enum)
IMPORTANT
This code is obsolete, refers to an obsolete version, and has moved to another project. This code and latest version are found in asherwunk/phabstractic. There’s also a current blog post on this.
I am proud to announce the addition of an enumeration data type to the Primus Artificial Intelligence Framework: Falcraft Module
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 elements, members 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 (the license of the Primus Artificial Intelligence Framework).
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( 'Enum->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"; } // Check to see if SplEnum is even available (still in SVN?) if (class_exists('SplEnum')) { $classCode .= "class $className extends \SplEnum implements \Countable{\n\n\t"; $classCode .= "private $value = null;\n\t"; /* Define the contants in the code from the values passed to the function */ foreach ( $values as $identifier => $val ) $classCode .= 'const ' . strtoupper($identifier) . " = $val;\n\t"; /* Define the default constant from the value passed to the function */ if ( isset( $this->conf->default ) && $this->conf->default ) $classCode .= "\nconst __default = self::" . $this->conf->default . ";\n\n"; /* Define our ->get function creating compatibility between the two enum classes */ $classCode .= "public function getEnum() { return \$this; }\n\t"; $classCode .= "public function get() { return \$this->value; }\n\t"; $classCode .= "public function set(\$value) { \$this->value = \$value; }\n\t"; } else { /* I guess SplEnum doesn't exist Create the class and its internal variable */ $classCode .= "class $className implements \Countable { private \$value;\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; foreach ($values as $identifier => $val) { $classCode .= "const $identifier = $val;\n\t"; // !$defaultTemp ? $defaultTemp = $identifier : null; } /* If the default option has been defined, use that one, otherwise Use the default default we defined above */ if (isset( $this->conf->default ) && $this->conf->default) { $classCode .= "\nconst __default = self::" . $this->conf->default . ";\n\n\t"; $classCode .= "\npublic static \$__sdefault = self::" . $this->conf->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->conf->default) { $classCode .= "self::\$__sdefault = self::" . $this->conf->default . ";"; } $classCode .= "\n \$reflector = new \\ReflectionClass(\$this); if (\$initValue) { if ( in_array( \$initValue, \$reflector->getConstants(), true ) ) { \$this->value = \$initValue; } else { throw new \UnexpectedValueException(\"Value not a const in enum $className\"); } } else { \$this->value = self::\$__sdefault; } } public function __toString() { return (string) \$this->value; } public function get() { return \$this->value; } // Enables kooky object typing stuff public function set( \$value ) { \$this->value = \$value; }\n"; } // How many enumerator categories are there? $classCode .= "public function count() { \$reflect = new \\ReflectionClass(get_class(\$this)); return count(\$reflect->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( 'Enum->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 the 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
photo credit: This can mean only one thing… via photopin (license)