1: <?php
2:
3: namespace GAubry\Enum;
4:
5: /**
6: * A simple-to-use PHP class that provides the ability to emulate and create type-safe enumerations.
7: *
8: * Definition:
9: * <code>
10: * class ColorEnum extends EnumAbstract {
11: * public static $RED;
12: * public static $GREEN;
13: * public static $BLUE;
14: * }
15: * </code>
16: *
17: * Usage:
18: * <code>
19: * // One call per enumeration is necessary and sufficient,
20: * // typically at the top of the program, during instantiation phase:
21: * ColorEnum::buildInstances();
22: *
23: * $color = ColorEnum::$RED;
24: * var_dump((string)$color); // string(3) "RED"
25: * var_dump(ColorEnum::$RED === ColorEnum::RED()); // true
26: *
27: * function f (ColorEnum $color) {
28: * switch($color) {
29: * case ColorEnum::$RED:
30: * …
31: * }
32: * …
33: * }
34: * f(ColorEnum::$RED);
35: * </code>
36: *
37: *
38: *
39: * Copyright (c) 2013 Geoffroy Aubry <geoffroy.aubry@free.fr>
40: * Licensed under the GNU Lesser General Public License v3 (LGPL version 3).
41: *
42: * @copyright 2013 Geoffroy Aubry <geoffroy.aubry@free.fr>
43: * @license http://www.gnu.org/licenses/lgpl.html
44: */
45: abstract class EnumAbstract
46: {
47: /**
48: * Global static counter to ensure that each EnumAbstract instance is unique.
49: * @var int
50: */
51: private static $iCounter = 0;
52:
53: /**
54: * A cache of all enum values to increase performance.
55: * Structure: <code>array(
56: * 'class' => array(
57: * 'key' => EnumAbstract instance,
58: * …
59: * ),
60: * …
61: * )</code>
62: *
63: * @var array
64: */
65: private static $aCache = array();
66:
67: /**
68: * A unique value to ensure that each EnumAbstract instance is unique.
69: * @var int
70: * @see self::$iCounter
71: */
72: private $iValue;
73:
74: /**
75: * Name of enum instance, used by __toString().
76: * @var string
77: * @see __toString()
78: */
79: private $sName;
80:
81: /**
82: * Constructor.
83: *
84: * @param string $sName Name of instance, used by __toString().
85: */
86: protected function __construct ($sName)
87: {
88: $this->iValue = self::$iCounter++;
89: $this->sName = $sName;
90: }
91:
92: /**
93: * Converts all static properties into EnumAbstract instances.
94: *
95: * @throws \BadMethodCallException if called directly on EnumAbstract instead of derived class
96: * @return string
97: */
98: public static function buildInstances ()
99: {
100: $sClass = get_called_class();
101: if (empty(self::$aCache[$sClass])) {
102: if ($sClass == get_class()) {
103: $sMsg = 'Method for only purpose of inherited classes!';
104: throw new \BadMethodCallException($sMsg, 1);
105: } else {
106: $oReflected = new \ReflectionClass($sClass);
107: foreach ($oReflected->getStaticProperties() as $sKey => $mValue) {
108: $sName = (empty($mValue) ? $sKey : (string)$mValue);
109: $oReflected->setStaticPropertyValue($sKey, new static($sName));
110: }
111: self::$aCache[$sClass] = $oReflected->getStaticProperties();
112: }
113: }
114: return $sClass;
115: }
116:
117: /**
118: * Returns an associative array containing all constants in the enum.
119: *
120: * @throws \BadMethodCallException if called directly on EnumAbstract instead of derived class
121: * @return array Associative array (name => instance, …)
122: */
123: public static function values()
124: {
125: $sClass = self::buildInstances();
126: return self::$aCache[$sClass];
127: }
128:
129: /**
130: * Returns the names (or keys) of all of constants in the enum.
131: *
132: * @throws \BadMethodCallException if called directly on EnumAbstract instead of derived class
133: * @return array Array of string
134: */
135: public static function keys()
136: {
137: return array_keys(self::values());
138: }
139:
140: /**
141: * Get enum instances with lazy instanciation.
142: * Triggered when invoking inaccessible methods in a static context.
143: *
144: * @SuppressWarnings(UnusedFormalParameter)
145: * @param string $sName name of the static property
146: * @param array $aArgs not used…
147: * @throws \DomainException if not called on one of the static properties
148: * @throws \BadMethodCallException if called directly on EnumAbstract instead of derived class
149: * @return EnumAbstract enum instance with the specified name
150: */
151: public static function __callStatic ($sName, $aArgs)
152: {
153: $sClass = self::buildInstances();
154: if (! isset(self::$aCache[$sClass][$sName])) {
155: $sMsg = "Unknown type '$sName'! Type must be in: "
156: . implode(', ', array_keys(self::$aCache[$sClass])) . '.';
157: throw new \DomainException($sMsg, 1);
158: }
159: return self::$aCache[$sClass][$sName];
160: }
161:
162: /**
163: * Returns the name of the instance.
164: *
165: * This magic method is used for setting a string value for the object.
166: * It will be used if the object is used as a string.
167: *
168: * @return string representing the object
169: */
170: public function __toString ()
171: {
172: return $this->sName;
173: }
174:
175: /**
176: * Prevent cloning.
177: *
178: * @throws \RuntimeException Cloning of this object isn't authorized!
179: */
180: final public function __clone()
181: {
182: throw new \RuntimeException('Cloning of this object isn\'t authorized!');
183: }
184: }
185: