Skip to content

Commit 02927ba

Browse files
authored
Merge pull request #21 from dbalabka/fix-for-abstract-classes
Throw an exception on non-final enum class and public constructor. Fix abstract classes initialisation.
2 parents ed7e7f1 + 373f2b7 commit 02927ba

20 files changed

+179
-50
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ matrix:
66
- php: '7.1'
77
- php: '7.2'
88
- php: '7.3'
9-
- php: '7.4snapshot'
9+
- php: '7.4'
1010

1111
before_install:
1212
- phpenv config-rm xdebug.ini || echo "No xdebug config."

examples/Enum/CardType.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
final class CardType extends Enumeration
99
{
10-
/** @var $this */
10+
/** @var self */
1111
public static $amex;
12-
/** @var $this */
12+
/** @var self */
1313
public static $visa;
14-
/** @var $this */
14+
/** @var self */
1515
public static $masterCard;
1616

1717
protected static function initializeValues() : void

examples/Enum/Circle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use Dbalabka\Enumeration\Examples\Struct\Point;
77

8-
class Circle extends Shape
8+
final class Circle extends Shape
99
{
1010
private Point $point;
1111
private float $radius;

examples/Enum/Color.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77

88
final class Color extends Enumeration
99
{
10-
/** @var $this */
10+
/** @var self */
1111
public static $red;
12-
/** @var $this */
12+
/** @var self */
1313
public static $green;
14-
/** @var $this */
14+
/** @var self */
1515
public static $blue;
1616

1717
private $value;
1818

19-
public function __construct(int $value)
19+
protected function __construct(int $value)
2020
{
2121
$this->value = $value;
2222
}

examples/Enum/Option.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* @author Dmitry Balabka <dmitry.balabka@gmail.com>
1212
* @template T
1313
*/
14-
class Option extends Enumeration
14+
abstract class Option extends Enumeration
1515
{
1616
/**
1717
* @psalm-var Option<mixed>

examples/Enum/Rectangle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use Dbalabka\Enumeration\Examples\Struct\Point;
77

8-
class Rectangle extends Shape
8+
final class Rectangle extends Shape
99
{
1010
private Point $pointA;
1111
private Point $pointB;

src/Enumeration/Enumeration.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Dbalabka\Enumeration\Exception\EnumerationException;
77
use Dbalabka\Enumeration\Exception\InvalidArgumentException;
88
use Dbalabka\StaticConstructorLoader\StaticConstructorInterface;
9+
use ReflectionClass;
910
use Serializable;
1011
use function array_search;
1112
use 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
)

tests/Enumeration/EnumerationTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
use Dbalabka\Enumeration\Tests\Fixtures\Action;
1010
use Dbalabka\Enumeration\Tests\Fixtures\ActionWithCustomStaticProperty;
1111
use Dbalabka\Enumeration\Tests\Fixtures\ActionWithPublicConstructor;
12+
use Dbalabka\Enumeration\Tests\Fixtures\EmptyEnum;
1213
use Dbalabka\Enumeration\Tests\Fixtures\Flag;
14+
use Dbalabka\Enumeration\Tests\Fixtures\NotFinalEnum;
15+
use Dbalabka\Enumeration\Tests\Fixtures\PublicConstructorEnum;
1316
use Error;
1417
use PHPUnit\Framework\Error\Warning;
1518
use PHPUnit\Framework\TestCase;
@@ -175,7 +178,7 @@ public function testCompareTo()
175178

176179
public function testCustomStaticProperties()
177180
{
178-
$this->markTestSkipped('Custom static properties does not allowed');
181+
$this->markTestSkipped('Custom static properties are not allowed');
179182
ActionWithCustomStaticProperty::initialize();
180183

181184
ActionWithCustomStaticProperty::$customProperty = ActionWithCustomStaticProperty::$edit;
@@ -202,4 +205,27 @@ public function testNameWhenIncorrectlyInitilizedProperies()
202205
$this->expectException(EnumerationException::class);
203206
$notOk->name();
204207
}
208+
209+
public function testNotFinalEnumShouldThrowAnException()
210+
{
211+
$this->expectException(EnumerationException::class);
212+
$this->expectExceptionMessage('Enumeration class should be declared as final');
213+
NotFinalEnum::initialize();
214+
NotFinalEnum::$testValue;
215+
}
216+
217+
public function testPublicConstructorShouldThrowAnException()
218+
{
219+
$this->expectException(EnumerationException::class);
220+
$this->expectExceptionMessage('Enumeration class constructor should not be public');
221+
PublicConstructorEnum::initialize();
222+
PublicConstructorEnum::$testValue;
223+
}
224+
225+
public function testOrdinalInitilizationFailedShouldThrowException()
226+
{
227+
$this->expectException(EnumerationException::class);
228+
$this->expectExceptionMessage('Ordinal initialization failed. Enum does not contain any static variables');
229+
EmptyEnum::initialize();
230+
}
205231
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Enumeration\Tests\Fixtures;
5+
6+
use Dbalabka\Enumeration\Enumeration;
7+
use function version_compare;
8+
use const PHP_VERSION;
9+
10+
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
11+
require_once __DIR__ . '/ActionProperties.php';
12+
} else {
13+
require_once __DIR__ . '/ActionTypedProperties.php';
14+
}
15+
16+
/**
17+
* Final is omitted for testing purposes
18+
*/
19+
abstract class AbstractAction extends Enumeration
20+
{
21+
use ActionProperties;
22+
}

tests/Enumeration/Fixtures/Action.php

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,7 @@
33

44
namespace Dbalabka\Enumeration\Tests\Fixtures;
55

6-
use Dbalabka\Enumeration\Enumeration;
7-
use function version_compare;
8-
use const PHP_VERSION;
9-
10-
if (version_compare(PHP_VERSION, '7.4.0beta', '<')) {
11-
require_once __DIR__ . '/ActionProperties.php';
12-
} else {
13-
require_once __DIR__ . '/ActionTypedProperties.php';
14-
}
15-
16-
/**
17-
* Final is omitted for testing purposes
18-
*/
19-
class Action extends Enumeration
6+
final class Action extends AbstractAction
207
{
21-
use ActionProperties;
8+
229
}

tests/Enumeration/Fixtures/ActionTypedProperties.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55

66
trait ActionProperties
77
{
8-
public static Action $view;
9-
public static Action $edit;
8+
public static AbstractAction $view;
9+
public static AbstractAction $edit;
1010
}

tests/Enumeration/Fixtures/ActionWithCustomStaticProperty.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
} else {
1212
require_once __DIR__ . '/ActionTypedProperties.php';
1313
}
14-
final class ActionWithCustomStaticProperty extends Action
14+
final class ActionWithCustomStaticProperty extends AbstractAction
1515
{
1616
use ActionProperties;
1717

tests/Enumeration/Fixtures/ActionWithPublicConstructor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
} else {
1313
require_once __DIR__ . '/ActionTypedProperties.php';
1414
}
15-
final class ActionWithPublicConstructor extends Action
15+
final class ActionWithPublicConstructor extends AbstractAction
1616
{
1717
public function __construct()
1818
{
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Enumeration\Tests\Fixtures;
5+
6+
use Dbalabka\Enumeration\Enumeration;
7+
8+
final class EmptyEnum extends Enumeration
9+
{
10+
protected function __construct()
11+
{
12+
$this->ordinal();
13+
}
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Enumeration\Tests\Fixtures;
5+
6+
use Dbalabka\Enumeration\Enumeration;
7+
8+
class NotFinalEnum extends Enumeration
9+
{
10+
public static $testValue;
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Enumeration\Tests\Fixtures;
5+
6+
use Dbalabka\Enumeration\Enumeration;
7+
8+
final class PublicConstructorEnum extends Enumeration
9+
{
10+
public static $testValue;
11+
12+
public function __construct()
13+
{
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Dbalabka\StaticConstructorLoader\Tests\Fixtures;
4+
5+
use Dbalabka\Enumeration\Enumeration;
6+
7+
abstract class AbstractEnumeration extends Enumeration
8+
{
9+
10+
}

tests/StaticConstructorLoader/Fixtures/Action.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<?php
22

3-
43
namespace Dbalabka\StaticConstructorLoader\Tests\Fixtures;
54

6-
75
use Dbalabka\StaticConstructorLoader\StaticConstructorInterface;
86

97
class Action implements StaticConstructorInterface
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Dbalabka\StaticConstructorLoader\Tests\Fixtures;
4+
5+
final class ChildOfAbstractEnumeration extends AbstractEnumeration
6+
{
7+
public static $instance;
8+
}

0 commit comments

Comments
 (0)