Tuesday 17 April 2012

PHP Easy Enums

So what do we have on the table today... ENUM. 
Coming from a Java background I really like the ideas of Enumerated types and when moving to PHP had a hard time accepting define(a global constant) OR constant within a class that you have to statically reference :p

So here is my enum behaviour class that was designed to be inherited into standard class is to allow for more intuitive enum coding

! This is a different way of defining enum as compared to php's SplEnum !

Let's look at how you would use it.. and then will take a look inside.

Example that represents HTTP status codes 

First lets create are enum collection.


! If the code looks a bit strange with all the slashes, it's because I'm use PHP autoloader.. which I love :D !

namespace enum\message;

final class Http extends \_Enumerat
{
 const OK = 200;
 const NO_CONTENT = 204;
 const BAD_REQUEST = 400;
 const UNAUTHORIZED = 401;
 const FORBIDDEN = 403;
 const NOT_FOUND = 404;
 const NOT_ACCEPTABLE = 406;
 const INTERNAL_ERROR = 500;
 const TEMPORARILY_OVERLOADED = 502;
}

and here's the filler

use \enum\message\Http;

//get the enum value directly
echo Http::OK; //int(200)

//get the value wrapped in an enum object
$serverProblem = Http::INTERNAL_ERROR();

//lets print the object
echo $serverProblem; //"500"

//let's check variables to see if there valid against the enums
$outSideRequest = '-1';

$status = 'invalid';

if(Http::check($outSideRequest))
{
 $status = 'valid';
}

echo 'the Request code is '.$status; //the Request code is invalid


//here we can enforce that functions must only accept a specific enum type
function setStatusCode(Http $_code){

}


If you want a bit more and have a good understanding of magic methods keep reading.



/**
* @note To help identify classes that cannot be instantiated we prefix them with a single underscore at the beginning
@note In order to help distinguish between variables passed into the function as against variables created inside it. We prefix the start of the variable name with an underscore.
*/
 abstract class _Enumerat
 {
  private $value;
  
There the interesting stuff of how you create your enum object.
As in the example above, all you need to do is call the value within the enum and add the function braces.

  /**
  * To help in force a standardisation on enum(as distinct from other objects) developer must invoke the enum constant value
  @see _Enumerat::__callStatic(a,b)
  */
  private function __construct($_value)
  {   
   $this->value = $_value;
  } 
  
  /**
  * This is invoked when the you want to create an enum. it will acts like a constructor
  */
  public static function __callStatic($_name, $_asString = FALSE)
  {
   $class = get_called_class();
   $oClass = new \ReflectionClass($class);
   $value = $oClass->getConstant($_name);
   
   if(NULL  === $value 
   OR FALSE === $value)//I'm raising an error as I consider this to be a compile time problem
   { trigger_error('invalid enum value',E_USER_ERROR); }
   else
   { return new $class($value); }
  }
  
The functions to accessing the value with in
  /**
   * This will convert this object instance into a string representing the enum if you try and use this object like a string
   * @return string
   */
  public function __toString()
  {
   return ''.$this();
  }

  
 /**
  * This allows you to quickly and easily access the value of the enum while keeping the enum object intact
  * @return mixed 
  */
  public function __invoke()
  {
   return $this->value;
  }
 }
  
Quick way to pull a list of all of the value the enum supports
  /**
   * This will return an array of all the predefined constants for this type of enum. The enum name will be the key and the enum's value... well, in value
   * @return array
   */
  public static function listValues()
  {
   $oClass = new \ReflectionClass(get_called_class());
   return $oClass->getConstants();
  }
Allows the resolving of the position of a value relative to wear it was declared.
 /**
 * Gets the position of this enum in the list as ordered in the source code.
 * @return int
 */
 public function index()
 {
  return $self::indexOf($this->value);
 }
 
 /**
 * Gets the position of the request enum as ordered in the source code.
 * @param int $_num the index you want to check for
 * @return int
 */
 public static function indexOf($_enum)
 {
  //if its an object of enum, then pull the value out of it
  if($_enum instanceof static)
  { $enum = $_enum(); }
  else
  { $enum = $_enum;  }
  
  $list = self::listValues();
  
  $count = 0;
  foreach($list as $val)
  {
   if($val === $enum)
   {
    return $count;
   }
   $count++;
  }
  return -1;
 }
Check if a value is valid
  /**
  * Will check if the input is compatible/valid with this instance of enum
  *
  * @param mixed $_enum you can pass in the enum object or the value of the enum
  * @return boolean
   */
  
  final public static function check($_enum){
  
   if($_enum instanceof static)
   { 
    return TRUE; 
   }
   else
   {
    return in_array($_enum, self::listValues() , TRUE);
   }
   return FALSE;
  }
  
  /**
  * Will let you know if an enum value exists in the list at the requested index
  *
  * @param int $_num the index you want to check for
  * @throws InvalidArgumentException if the input is not an int
  * @return boolean true if exists false if not
  */
  public static function checkIndex($_num)
  {
   if( ! is_int($_num))
   { 
    throw new \InvalidArgumentException('input is not an integer'); 
   }
   
   $count = count(self::listValues());
   if(0 <= $_num AND $_num < $count)
   { 
    return TRUE; 
   }
   return FALSE;
  }
  
And that it, easy peasy.
... continue reading!