Skip to content

Modify the enum implementation for compatibility with PHP 8.1 enumeration #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ $action = Action::VIEW();
$action = Action::$key();
// or with a dynamic value:
$action = Action::from($value);
// or
$action = new Action($value);
```

As you can see, static methods are automatically implemented to provide quick access to an enum value.
Expand All @@ -67,7 +65,6 @@ function setAction(Action $action) {

## Documentation

- `__construct()` The constructor checks that the value exist in the enum
- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant)
- `getValue()` Returns the current value of the enum
- `getKey()` Returns the key of the current value on Enum
Expand Down Expand Up @@ -111,7 +108,7 @@ final class Action extends Enum
* @return Action
*/
public static function VIEW() {
return new Action(self::VIEW);
return self::from(self::VIEW);
}
}
```
Expand Down
156 changes: 76 additions & 80 deletions src/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @author Matthieu Napoli <matthieu@mnapoli.fr>
* @author Daniel Costa <danielcosta@gmail.com>
* @author Mirosław Filip <mirfilip@gmail.com>
* @author Alexandru Pătrănescu <drealecs@gmail.com>
*
* @psalm-template T
* @psalm-immutable
Expand All @@ -27,7 +28,7 @@ abstract class Enum implements \JsonSerializable
* @var mixed
* @psalm-var T
*/
protected $value;
private $value;

/**
* Enum key, the constant name
Expand All @@ -43,73 +44,86 @@ abstract class Enum implements \JsonSerializable
* @var array
* @psalm-var array<class-string, array<string, mixed>>
*/
protected static $cache = [];
private static $cache = [];

/**
* Cache of instances of the Enum class
*
* @var array
* @psalm-var array<class-string, array<string, static>>
*/
protected static $instances = [];
private static $instances = [];

/**
* Creates a new value of some type
*
* @psalm-pure
* @param string $key
* @param mixed $value
*
* @psalm-param T $value
* @throws \UnexpectedValueException if incompatible type is given.
*/
public function __construct($value)
final private function __construct(string $key, $value)
{
if ($value instanceof static) {
/** @psalm-var T */
$value = $value->getValue();
}

/** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */
$this->key = static::assertValidValueReturningKey($value);

/** @psalm-var T */
$this->key = $key;
$this->value = $value;
}

/**
* This method exists only for the compatibility reason when deserializing a previously serialized version
* that didn't had the key property
* The single place where the instance is created, other than unserialize
*
* @psalm-pure
* @param string $key
* @param mixed $value
* @return static
*/
public function __wakeup()
private static function getInstance(string $key, $value): self
{
/** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */
if ($this->key === null) {
/**
* @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm
* @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed
*/
$this->key = static::search($this->value);
if (!isset(self::$instances[static::class][$key])) {
return self::$instances[static::class][$key] = new static($key, $value);
}

return clone self::$instances[static::class][$key];
}

/**
* @param mixed $value
* @return static
* @psalm-return static<T>
*/
public static function from($value): self
final public static function from($value): self
{
$key = static::assertValidValueReturningKey($value);
$key = self::search($value);

if ($key === false) {
throw new \UnexpectedValueException("Value '{$value}' is not part of the enum " . static::class);
}

return self::getInstance($key, $value);
}

/**
* @param mixed $value
* @return static|null
* @psalm-return static<T>|null
*/
final public static function tryFrom($value): ?self
{
$key = self::search($value);

if ($key === false) {
return null;
}

return self::__callStatic($key, []);
return self::getInstance($key, $value);
}

/**
* @psalm-pure
* @return mixed
* @psalm-return T
*/
public function getValue()
final public function getValue()
{
return $this->value;
}
Expand All @@ -120,7 +134,7 @@ public function getValue()
* @psalm-pure
* @return string
*/
public function getKey()
final public function getKey(): string
{
return $this->key;
}
Expand All @@ -130,7 +144,7 @@ public function getKey()
* @psalm-suppress InvalidCast
* @return string
*/
public function __toString()
final public function __toString(): string
{
return (string)$this->value;
}
Expand All @@ -140,14 +154,13 @@ public function __toString()
* Returns false if an argument is an object of different class or not an object.
*
* This method is final, for more information read https://github.com/myclabs/php-enum/issues/4
*
* @psalm-pure
* @psalm-param mixed $variable
* @param static $variable
* @return bool
*/
final public function equals($variable = null): bool
final public function equals(self $variable): bool
{
return $variable instanceof self
return $variable instanceof static
&& $this->getKey() === $variable->getKey()
&& $this->getValue() === $variable->getValue()
&& static::class === \get_class($variable);
}
Expand All @@ -157,11 +170,10 @@ final public function equals($variable = null): bool
*
* @psalm-pure
* @psalm-return list<string>
* @return array
*/
public static function keys()
final public static function keys(): array
{
return \array_keys(static::toArray());
return \array_keys(self::toArray());
}

/**
Expand All @@ -171,13 +183,12 @@ public static function keys()
* @psalm-return array<string, static>
* @return static[] Constant name in key, Enum instance in value
*/
public static function values()
final public static function values(): array
{
$values = array();
$values = [];

/** @psalm-var T $value */
foreach (static::toArray() as $key => $value) {
$values[$key] = new static($value);
foreach (self::toArray() as $key => $value) {
$values[$key] = self::getInstance($key, $value);
}

return $values;
Expand All @@ -192,18 +203,22 @@ public static function values()
* @psalm-return array<string, mixed>
* @return array Constant name in key, constant value in value
*/
public static function toArray()
final public static function toArray(): array
{
$class = static::class;

if (!isset(static::$cache[$class])) {
if (!isset(self::$cache[$class])) {
/** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */
$reflection = new \ReflectionClass($class);
$reflection = new \ReflectionClass($class);
/** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */
static::$cache[$class] = $reflection->getConstants();
if (!$reflection->isFinal()) {
throw new \ParseError("Class " . $class . " is not declared final");
}
/** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */
self::$cache[$class] = $reflection->getConstants();
}

return static::$cache[$class];
return self::$cache[$class];
}

/**
Expand All @@ -215,7 +230,7 @@ public static function toArray()
* @psalm-assert-if-true T $value
* @return bool
*/
public static function isValid($value)
final public static function isValid($value): bool
{
return \in_array($value, static::toArray(), true);
}
Expand All @@ -227,39 +242,24 @@ public static function isValid($value)
* @psalm-assert T $value
* @param mixed $value
*/
public static function assertValidValue($value): void
{
self::assertValidValueReturningKey($value);
}

/**
* Asserts valid enum value
*
* @psalm-pure
* @psalm-assert T $value
* @param mixed $value
* @return string
*/
private static function assertValidValueReturningKey($value): string
final public static function assertValidValue($value): void
{
if (false === ($key = static::search($value))) {
if (!self::isValid($value)) {
throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class);
}

return $key;
}

/**
* Check if is valid enum key
*
* @param $key
* @param string $key
* @psalm-param string $key
* @psalm-pure
* @return bool
*/
public static function isValidKey($key)
final public static function isValidKey(string $key): bool
{
$array = static::toArray();
$array = self::toArray();

return isset($array[$key]) || \array_key_exists($key, $array);
}
Expand All @@ -273,7 +273,7 @@ public static function isValidKey($key)
* @psalm-pure
* @return string|false
*/
public static function search($value)
final public static function search($value)
{
return \array_search($value, static::toArray(), true);
}
Expand All @@ -282,7 +282,7 @@ public static function search($value)
* Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant
*
* @param string $name
* @param array $arguments
* @param array $arguments
*
* @return static
* @throws \BadMethodCallException
Expand All @@ -291,16 +291,12 @@ public static function search($value)
*/
public static function __callStatic($name, $arguments)
{
$class = static::class;
if (!isset(self::$instances[$class][$name])) {
$array = static::toArray();
if (!isset($array[$name]) && !\array_key_exists($name, $array)) {
$message = "No static method or enum constant '$name' in class " . static::class;
throw new \BadMethodCallException($message);
}
return self::$instances[$class][$name] = new static($array[$name]);
$array = self::toArray();
if (!isset($array[$name]) && !\array_key_exists($name, $array)) {
$message = "No static method or enum constant '$name' in class " . static::class;
throw new \BadMethodCallException($message);
}
return clone self::$instances[$class][$name];
return self::getInstance($name, $array[$name]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/EnumConflict.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @author Daniel Costa <danielcosta@gmail.com>
* @author Mirosław Filip <mirfilip@gmail.com>
*/
class EnumConflict extends Enum
final class EnumConflict extends Enum
{
const FOO = "foo";
const BAR = "bar";
Expand Down
2 changes: 1 addition & 1 deletion tests/EnumFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* @author Daniel Costa <danielcosta@gmail.com>
* @author Mirosław Filip <mirfilip@gmail.com>
*/
class EnumFixture extends Enum
final class EnumFixture extends Enum
{
const FOO = "foo";
const BAR = "bar";
Expand Down
Loading