« Posts tagged object oriented

Java style enums in PHP

Ever since I learned how enums worked in java, I completely fell in love with those features. PHP has a noticeable lack of enums, it only has consts and defines. Defines have the problem that they are always in global scope, and so are very prone to namespace collisions. Consts are the answer to this, but they still lack many of the features that java style enums have. So, to correct this problem, I created a class in PHP that offers all the functionality that java enums do. I call it JEnum:

abstract class JEnum{
    /**
     * Returns the name of this enum. The only difference between this method
     * and getName, is that this method can be overwritten by the extending class,
     * whereas getName is final.
     * @param integer $enum
     * @return string 
     */
    public static function toString($enum){
        self::_selfRef();
        $ref = new ReflectionClass(get_called_class());
        foreach($ref->getConstants() as $name => $constant){
            if($enum == $constant){
                return $name;
            }
        }        
        return null;
    }
    
    /**
     * Returns the name of this enum, exactly as defined in the class.
     * @param integer $enum
     * @return string 
     */
    public static final function name($enum){
        self::_selfRef();
        $ref = new ReflectionClass(get_called_class());
        foreach($ref->getConstants() as $name => $constant){
            if($enum == $constant){
                return $name;
            }
        }        
        return null;
    }
    
    /**
     * This function should be called by the class right after it is declared.
     * It will ensure that all parameters are appropriate by inspecting the
     * class reflectively.
     */
    public static final function init(){
        self::_selfRef();
        $ref = new ReflectionClass(get_called_class());
        $consts = array();
        foreach($ref->getConstants() as $constant){
            if(in_array($constant, $consts)){
                trigger_error("Multiple constants defined with the same value in ".  get_called_class(), E_USER_ERROR);
            }
            if(!is_numeric($constant)){
                trigger_error("All constants defined in ".get_called_class()." must be integer values", E_USER_ERROR);
            }
            $consts[] = $constant;
        }        
    }
    
    /**
     * Returns the name of the enum class.
     * @return string 
     */
    public static final function getDeclaringClass(){
        self::_selfRef();
        return get_called_class();
    }
    
    /**
     * Returns the numerical value of the enum, given it's name as a string
     * @param string $value The name of the enum, as a string
     * @return integer The integer value, or null, if it doesn't exist
     */
    public static function valueOf($value){
        self::_selfRef();
        $ref = new ReflectionClass(get_called_class());
        foreach($ref->getConstants() as $name => $constant){
            if($name == $value){
                return $constant;
            }
        }
        return null;
    }
    
    /**
     * This class should not be used directly, but only extended then used.
     */
    private static function _selfRef(){
        if(get_called_class() == __CLASS__){
            trigger_error("JEnum cannot be used directly", E_USER_ERROR);
        }
    }
    
    /**
     * Don't allow instantiation of the class
     */
    private final function __construct() {}
    
}

In order to use the enums, you create a new class that extends JEnum, and you define integer constants:

class TestEnum extends JEnum{
    const myEnum = 1;
    const anotherEnum = 2;
    const yetAnotherEnum = 3;
}

I have not figured out a way to automatically have the class be initialized, simply by extending the JEnum class, so the next step, after including your class is to call init on it.

include("TestEnum.php");
TestEnum::init();

This will verify that your enum follows the rules that an enum typically would: You can’t have duplicate values, and the consts can only be defined as integers. After these two steps, you can use the enums like normal constants, except now you also have the additional benefit of all the functions defined in JEnum. You can overwrite toString, but not name, just like a java enum. Here is example usage, once you create your class:

echo TestEnum::toString(TestEnum::anotherEnum);
echo "<br />";
echo TestEnum::valueOf("yetAnotherEnum");