« Posts tagged java

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");

Programmatic Testing

So, in my Minecraft plugin, I would like to start adding JUnit tests, but I’ve come across an issue that is preventing me from actually making many test cases. There are two issues. My plugin runs as a library inside of another plugin. I could in theory make a test harness, so it can run standalone, but for the most part, it would be nearly impossible to generate test data without it running in a real case, and writing an effective test harness that generated real data to send to the plugin, and returned valid, programmatically verifiable results would be a massive undertaking in and of itself. Secondly, many functions in the program return void, but do some action. For instance, how would one test the following function using JUnit?

public void add(int i, int j){
     System.out.println(i + " plus " + j + " is " + (i + j));
}

This would be a simple function to write a test case for, if only it returned what it computed! The first though is of course to change the function to return the result, as well as printing it to the screen. However, there are two issues: what if this function implemented an abstract function, or overwrote some parent class’s function. In this case, I cannot change the signature. Secondly, what if the results are not programmatically identifiable anyways, for instance, in Minecraft, if you send a message to a player, you would write some code similar to this:

public void sendMessage(int some_input){
     //some_input determines what player we send a message to, and what message to send
     Player p = null;
     String message = "";
     switch(some_input){
          case 1:
               p = new Player("Some player");
               message = "Some message";
           //more cases...
     }
     p.sendMessage(message);
}

In this abstract example, we could change the function to return either the player we send to, or the message we sent, but not both, at least not without doing some serious work on the function. Furthermore, this breaks the paradigm of keeping your tests separate from your production code.

I am genuinely interested in getting these test cases working, because doing regression testing when I add a new feature is starting to get incredibly cumbersome, but in many many cases in my plugin, I have issues like this, where I can’t really figure out how to best test the function, without having a whole slew of tests to manually run. It’s quickly becoming apparent to me that manually testing becomes unscalable, and that a programmatic solution is the only solution.