<?php
namespace Co3\CodeGen;
use \InvalidArgumentException as InvalidArgumentException;
use \BadMethodCallException as BadMethodCallException;
/**
* @package Co3
* @subpackage CodeGen
* @author Oliver Anan <oliver@ananit.de>
*/
/**
* Base class of enumerations.
* Service provider for generating Enumerations.
*
* <p>This class can generate other enum classes at runtime and provides methods to work with enums.
* All enum classes are derived from this class.</p>
* <p>Creation and managment are not seperated to allow the usage
* of a protected constructor.</p>
* <p>Creating a new enumeration involves craeting the source code and one instance
* for each member.</p>
* <p><b>Manually deriving from this class will not work</b>.</p>
* <p>Supports type hinting
* <code>
* //create the enumearion 'Direction'
* Enum::create('Direction',array('Up','Down','Left','Right'));
*
* function move(Direction $dir,$distance){
* //code
* }
* </code></p>
* <p>implements __toString()</p>
* @author Oliver Anan <oliver@ananit.de>
* @version 1.0
* @since 0.1
* @copyright 2011 oliver anan
* @license LGPL 3 http://www.gnu.org/licenses/lgpl.html
*/
class Enum{
//------ STATIC ------//
private static $_members = array();
private static $_arrays = array();
/**
* Create a new enumeration class.
*
* <p>if the class name contains a \ character the class will be generated in a namespace.
* Everything before the last \ is considered as namespace</p>
* <p>All values of the $members array must be valid php identifiers.
* Values o the $members array must not be PHP keywords.
* The keys of the $members array are preserved unless $baseTwoKeysm is true</p>
* <code>
* //create the enumearion 'Direction' in the namespace 'My\Namespace'
* Enum::create('My\Namespae\Direction',array('Up','Down','Left','Right'));
* </code>
* @param string $name the name of the class including the namespace
* @param array $members an array containng all memebers of the enumeration.
* The keys are preserved unless baseTwoKeys is true.
* @param boolean $baseTwoKeys If this is true all keys will be replaced with powers of 2.
* @return void;
* @static
* @throws \InvalidArgumentException
* @throws Co3\Exception\FormatException (Only if class is available)
* @access public
*/
public static function create($name, $members,$baseTwoKeys=false){
$code = self::generateSourceCode($name,$members);
eval($code);
//create instances
self::$_arrays[$name] = array();
self::$_members[$name] = array();
if($baseTwoKeys){
$i = 0;
}
foreach($members as $key => $member){
if($baseTwoKeys){
$key = pow(2,$i++);
}
$instance = new $name($key,$member);
self::$_members[$name][$member] = $instance;
self::$_arrays[$name][$key] = $instance;
}
}
/**
* get an array with all instances of this enumeration.
* @return array
* @access public
* @static
* @throws \BadMethodCallException
*/
public static function toArray(){
$calledClass = get_called_class();
if($calledClass==__CLASS__){
throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only be called from deriver classes.');
}
return self::$_arrays[get_called_class()];
}
/**
* create the php source code for an enum class
*
* @param $name the name o the new class including the namespace.
* @param $members
* @return string
* @access private
* @static
* @throws \InvalidArgumentException
* @throws Co3\Exception\FormatException (Only if class is available)
*/
private static function generateSourceCode($name,$members){
$code = "";
//remove leading '\'
$name = ltrim($name,'\\');
$parts = explode('\\',$name);
//validate class name (including namespace)
foreach($parts as $part){
if(!self::isValidIdentifier($part)){
$message = "Invalid class/namespace name '{$className}'";
if(class_exists('Co3\Exception\FormatException',true)){
throw new Co3\Exception\FormatException($message);
} else {
throw new InvalidArgumentException($message);
}
}
}
$className = array_pop($parts);;
$namespace = implode('\\',$parts);
//create source code
if($namespace){
$code .= "namespace {$namespace};\n";
}
$code .= "final class {$className} extends \\" . __CLASS__ . "{\n";
foreach($members as $key => $member){
if(!self::isValidIdentifier($member)){
$message = "Invalid member name '{$member}' at index {$key}.";
if(class_exists('Co3\Exception\FormatException')){
throw new Co3\Exception\FormatException($message);
} else {
throw new InvalidArgumentException($message);
}
}
$code .= "const {$member} = '{$member}';\n";
}
$code .= "}";
return $code;
}
/**
* Get an instance of the called enum class.
*
* <p>Return an instance of the called class if there is an instance
* with an name that equals $name.</p>
* <p>Enumeations define class constants for every member.
* You can use the name or the constant to get an instance</p>
* <p>Subsequent calls to get with the same $name will return the same instance</p>
* <code>
* Enum::create('Direction',array('Up','Down','Left','Right'));
* $up = Direction::get(Direction::Up);
* //is the same as
* $upToo = Direction::get('Up');
* //There is just one instance for every member.
* $up === $upToo //true
* </code>
* <p>If there is no matching instance a InvalidArgumentException is thrown.</p>
* @param string $name the name of the instance.
* @return unknown_type an instance of the called class
* @access public
* @static
* @throws \BadMethodCallException
*/
public static function get($name){
$calledClass = get_called_class();
if($calledClass==__CLASS__){
throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
}
if ($calledClass::isMember($name)){
return Enum::$_members[$calledClass][$name];
} else {
throw new InvalidArgumentException("{$name} is no valid member of {$calledClass}");
}
}
/**
* Get an instance of the called enum class.
*
* Return an instance of the called class if there is an instance
* with an key that equals $key.
* If there is no matching instance a InvalidArgumentException is thrown.
* @param $key the key of the instance.
* @access public
* @static
* @return unknown_type an instance o the called class
* @throws \BadMethodCallException
*/
public static function getByKey($key){
$calledClass = get_called_class();
if($calledClass==__CLASS__){
throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
}
if($calledClass::isKey($key)){
return self::$_arrays[$calledClass][$key];
} else {
throw new InvalidArgumentException("{$name} is no valid key of {$calledClass}");
}
}
/**
* Returns an boolean indicating if there is a member of this enum type with this name.
* @param $name
* @return boolean
* @access public
* @static
* @throws \BadMethodCallException
*/
public static function isMember($name){
$calledClass = get_called_class();
if($calledClass==__CLASS__){
throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only be called from deriver classes.');
}
return array_key_exists($name,Enum::$_members[$calledClass]);
}
/**
* Returns an boolean indicating if there is a member of this enum type with this key.
* @return boolean
* @access public
* @static
* @throws \BadMethodCallException
*/
public static function isKey($key){
$calledClass = get_called_class();
if($calledClass==__CLASS__){
throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
}
return array_key_exists($key, self::$_arrays[$calledClass]);
}
/**
* Returns an boolean indicating if the given string is a valid PHP identifier
*
* @todo move this method to another class
* @param $string
* @return unknown_type
* @access protected
* @static
*/
private static function isValidIdentifier($string){
return preg_match("|^[a-zA-Z_][a-zA-Z0-9_]*$|i", $string);
}
//------ INSTANCE ------//
/**
* The internal key of this instance
* @var unknown_type
* @access protected
*/
private $key;
/**
* The name of this instance
* @var unknown_type
* @access protected
*/
private $name;
/**
* constructs a new instance
* @param $key the key o the instance
* @param $name the name o the instance
* @return unknown_type
* @access protected
*/
protected function __construct($key, $name){
$this->key = $key;
$this->name = $name;
}
/**
* Returns the internal key of this instance.
* @return unknown_type
* @access public
*/
public function getKey(){
return $this->key;
}
/**
* returns the name of this instance.
* @return string
* @access public
*/
public function __toString(){
return $this->name;
}
}
?>
|