66use Dbalabka \Enumeration \Exception \EnumerationException ;
77use Dbalabka \Enumeration \Exception \InvalidArgumentException ;
88use Dbalabka \StaticConstructorLoader \StaticConstructorInterface ;
9+ use ReflectionClass ;
910use Serializable ;
1011use function array_search ;
1112use function get_class_vars ;
@@ -31,6 +32,9 @@ abstract class Enumeration implements StaticConstructorInterface, Serializable
3132 /** @var array */
3233 private static $ initializedEnums = [];
3334
35+ /**
36+ * @throws EnumerationException
37+ */
3438 final public static function __constructStatic () : void
3539 {
3640 if (self ::class === static ::class) {
@@ -44,9 +48,23 @@ final public static function initialize(): void
4448 if (isset (self ::$ initializedEnums [static ::class])) {
4549 return ;
4650 }
47- self ::$ initializedEnums [static ::class] = true ;
51+
52+ /**
53+ * Using reflections we can enforce the strict rules of enums declaration.
54+ * It should not rise performance issue because enumeration class initialization executes only once.
55+ */
56+ $ classReflection = new ReflectionClass (static ::class);
57+ if (!$ classReflection ->isAbstract () && !$ classReflection ->isFinal ()) {
58+ throw new EnumerationException ('Enumeration class should be declared as final ' );
59+ }
60+ $ method = $ classReflection ->getConstructor ();
61+ if ($ method && $ method ->isPublic ()) {
62+ throw new EnumerationException ('Enumeration class constructor should not be public ' );
63+ }
64+
4865 static ::initializeValues ();
4966 static ::initializeOrdinals ();
67+ self ::$ initializedEnums [static ::class] = true ;
5068 }
5169
5270 final protected static function initializeOrdinals () : void
@@ -63,6 +81,14 @@ final protected static function initializeOrdinals() : void
6381 */
6482 protected static function initializeValues () : void
6583 {
84+ /**
85+ * TODO: Unfortunately, it is impossible to avoid class reflection here.
86+ * The reflection caching here should not give major performance benefit.
87+ * It should be properly benchmarked.
88+ */
89+ if ((new ReflectionClass (static ::class))->isAbstract ()) {
90+ return ;
91+ }
6692 $ firstEnumItem = new static ();
6793
6894 $ staticVars = static ::getEnumStaticVars ($ firstEnumItem );
@@ -116,6 +142,9 @@ protected function __construct()
116142 {
117143 }
118144
145+ /**
146+ * @throws EnumerationException
147+ */
119148 final public function ordinal () : int
120149 {
121150 if (null === $ this ->ordinal ) {
@@ -131,9 +160,15 @@ final public function ordinal() : int
131160 $ ordinal ++;
132161 }
133162 }
163+ if ($ this ->ordinal === null ) {
164+ throw new EnumerationException ('Ordinal initialization failed. Enum does not contain any static variables ' );
165+ }
134166 return $ this ->ordinal ;
135167 }
136168
169+ /**
170+ * @throws EnumerationException
171+ */
137172 final public function compareTo (self $ enum ) : int
138173 {
139174 return $ this ->ordinal () - $ enum ->ordinal ();
@@ -154,7 +189,7 @@ final public function name() : string
154189 if (false === $ name ) {
155190 throw new EnumerationException (
156191 sprintf (
157- 'Can not find $this in $ s::values(). ' .
192+ 'Can not find $this in % s::values(). ' .
158193 'It seems that the static property was overwritten. This is not allowed. ' ,
159194 get_class ($ this )
160195 )
0 commit comments