Ashley Sheridan​.co.uk

PHP EnumType: an Alternative to SplEnum

Posted on

When it comes to data types, PHP doesn't come with much out of the box. That's where SPL comes in, a collection of more complex object types and interfaces.

One of the types that it comes with is SplEnum which is part of the SplTypes and is not installed by default. On Linux and Unix systems, this isn't a major problem, as there are available packages to install it easily, but if you're running PHP on Windows then you might have problems, as the only way to get these extra types is to compile PHP yourself.

Compiling PHP is a lot of work though, so I wrote a small class to replace SplEnum and made it available on GitHub.

the constructor

There are several parts to the constructor, but perhaps the most important (and unusual) is the use of ReflectionClass as a means of getting the list of constants within the concrete class. It uses this list to verify that an instance of the concrete class is being initialised with a value that is allowed in that set list of constants when the constructor is passed a value. Then there is one further check to see if the __default constant exists if the concrete class is initialised _without_ a constructor argument. Personally, I would always prefer to initialise any enumerable type with an explicit value, but this is part of the behaviour of SplEnum, so I've replicated it here.

public function __construct(int $value = null) { $reflectionClass = new ReflectionClass($this); $constants = $reflectionClass->getConstants(); $instanceClassName = get_called_class(); if($value === null) { if(!isset($constants["__default"])) throw new UnexpectedValueException("Default value not defined and no initialisation value given in $instanceClassName"); $this->value = $constants["__default"]; } else { $allowedValues = array_values($constants); if(!in_array($value, $allowedValues)) throw new UnexpectedValueException("Value not in const list in $instanceClassName"); $this->value = $value; } }

If an incorrect initialisation value is used in the constructor, or no value was passed and there exists no __default constant in the concrete class, then the constructor will throw an UnexpectedValueException. This is also done to match the original SplEnum class.

What this means is that the constructor behaves just like the original:

class Month extends EnumType { const __default = self::January; const January = 1; const February = 2; const March = 3; const April = 4; const May = 5; const June = 6; const July = 7; const August = 8; const September = 9; const October = 10; const November = 11; const December = 12; } $month = new Month(Month::June);

basic getters

The SplEnum class returns the numerical value it was initialised with if you try to use the instance as a value. Unfortunately, PHP only has the magic __toString() method which can be used for this, meaning the output isn't a perfect like for like match. To this end, I added a __toInt() method (who knows, maybe this may be added in the future as a magic method) to return the numerical value:

public function __toString() { return (string)$this->value; } public function __toInt() { return $this->value; }

The methods are used like this:

$month = new Month(Month::June); // outputs 6 // uses the __toString() magic method as it's used in a string context echo $month . PHP_EOL; // outputs 7 // returns the number 6, performs the addition, then PHP outputs that as a string echo $month->__toInt() + 1; //

implementing the getconstlist method

The final thing to implement is the getConstList method, which returns a list of all the possible constants in an instance. For this I again used the ReflectionClass as I did in the constructor:

public function getConstList(bool $includeDefault = false) { $reflectionClass = new ReflectionClass($this); $constants = $reflectionClass->getConstants(); if($includeDefault) return $constants; unset($constants["__default"]); return $constants; }

Because the ReflectionClass is returning a new copy of the constants as an array, I can just call unset($constants["__default"]); to remove it (even if it doesn't exist). No notice or error will be thrown in this case because $constants will always exist, so it doesn't actually matter if __default does or not.

conclusion

This isn't going to be as fast as SplEnum, and it's only about 99% compatible, but this does serve as a suitable alternative on those systems where you just can't use SplEnum. You can get it yourself from the GitHub repo. I've added basic documentation, examples, and tests to accompany it. You will need zend.assertions enabled in your php.ini for the tests assertions to work correctly.