A PHP Enum class: code projected on a face

Here’s one approach that addresses the current lack of a native PHP enum class.

An Enum type allows you to define and enforce a limited set of values, and to encapsulate a selection from the constrained list. The key here is enforcement — you want to be able to insist that the user of a class can’t pass in arbitrary values. Say, for example, you have a class that accepts one of these values into its constructor: MYSQL, SQLITE, FILES. You really don’t want someone to pass in the value POSTGRES — at least not until you’re ready to handle it.

Java provides a specialised Enum type for this purpose. In the PHP world, we tend to be a bit more ad hoc about these things. Here’s a typical solution:

class StoreThing {

    const DB_MYSQL=1;
    const DB_SQLITE=2;
    const DB_FILES=3;
    private $type;

    function __construct( $storenum ) { 
        $this->type = $storenum;
    }   
}

This is easy to use from the client’s perspective. Calling code can call up the relevant integer using the plain English constant name:

new StoreThing( StoreThing::DB_MYSQL );

But as the class author we are taking a lot on trust. We can’t stop a confused user doing something like:

new StoreThing( 2000 );

Of course we can add some testing into the constructor and throw an exception if the provided value is unexpected. That’s pretty old school, however. PHP 5 gave us type hinting to minimise manual type checking. Besides if we want to use the the same flags in multiple locations we could find ourselves copy/pasting the value check routine — with all the problems that code duplication brings.

What we really need is a PHP enum class that both constrains choice and embeds the chosen value. Then our StoreThing class can use a simple type hint to ensure that it’s being passed something sane.

I had to solve this problem yet again today. I came up with EnumType. An abstract class that demands a single method implementation. getFields() should return a simple array of string values. Here’s my extending class:

class StoreFields extends EnumType {
    public function getFields() {
        return array( "MYSQL", "SQLITE", "FILES" );
    }   
}

Pretty simple, right? So what does this win us? Well, just because of that array, the StoreFields class now has magic static methods: MYSQL(), SQLITE(), and FILES(). You might expect these to return string values. Actually, that wouldn’t be terribly useful because once we’ve returned a string we’re back in the realm of arbitrary values. In fact, each of these methods returns an instance of the StoreFields class primed with its corresponding string.

This is much more simple than it sounds. Here I create a StoreFields object primed with the MYSQL value.

$flag = StoreFields::MYSQL();
print get_class( $flag )."\n";     // prints 'StoreFields'
print $flag->value()."\n";         // prints 'MYSQL' 
print "$flag\n";                   // prints 'MYSQL'  (thanks to __toString())

$flag2 = new StoreFields("MYSQL"); // equivalent to $flag instantiation above

So now the StoreThing class can demand and use StoreFields.

class StoreThing {
    private $type;

    function __construct( StoreFields $fields ) {
        // $fields->value() can only be one of MYSQL, SQLITE, FILES
        $this->type = $fields;
    }
}

This illustrates the useful duality of the PHP Enum class. The StoreFields type both constrains values and contains a value. Here’s how you might instantiate StoreThing:

$store = new StoreThing( StoreFields::FILES() );

So, from a client perspective, just as simple as before. But with added security from a system perspective because, if I try to use unsupported values, the EnumType superclass will complain:

StoreFields::MONKEYSCRATCHING(); // Exception: 'unknown type value: MONKEYSCRATCHING'
new StoreFields( "RANDOMHAIR" ); // Exception: 'unknown type value: RANDOMHAIR'

OK.. so how massive must the EnumType base class be to handle all that magic? Thanks to a combination of late static bindings and an interceptor method it’s actually a piece of cake:

abstract class EnumType {
    private $val;

    public abstract function getFields();

    final function __construct( $str ) { 
        if ( ! in_array( $str,  $this->getFields() ) ) { 
            throw new \Exception("unknown type value: $str");
        }   
        $this->val = $str;
    }   

    public static function __callStatic( $func, $args ) { 
        return new static( $func ); 
    }

    public function value() {
        return $this->val;
    }   

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

This PHP enum solution is pretty minimal, I think, for what we get. I might follow up with a short analysis of the EnumType class, or I might move on to new shiny things. If it matters to you, let me know in the comments or on Twitter — @getinstance_mz.

Either way, this was the first in an occasional series of getInstance(*) code posts which will cover tricks that make my coding life easier.

Tags:

Categories:

Updated: