Skip to content

Instantly share code, notes, and snippets.

@lsloan
Last active April 12, 2024 01:02
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lsloan/72f94712eca20fbe94cd to your computer and use it in GitHub Desktop.
Save lsloan/72f94712eca20fbe94cd to your computer and use it in GitHub Desktop.
BetterEnum.php: A pure-PHP alternative to SplEnum, although only a 99% compatible replacement for it.
<?php
/**
* Class BetterEnum
*
* A pure-PHP alternative to SplEnum, although only a 99% compatible replacement for it.
*
* See: http://php.net/manual/en/class.splenum.php
*
* To declare an enum class, subclass BetterEnum and define the names and values as constants.
* If the enum is to allow instantiation with a null value, it needs to have a default value
* defined in the constant "__default".
*
* TODO: Verify PHP 5.5 compatibility
* Since it was tested with PHP 5.6, this class may not be 100% compatible with PHP 5.5 and
* other versions from recent history. Also, although it's not the fault of this class,
* PHP 5.5 doesn't allow defining constants with a calculated value. E.g., this is illegal
* in PHP 5.5:
*
* const JSON_OPTIONS = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES;
*
* Although it is allowed in PHP 5.6. It might be helpful to provide support for calculated
* values in older versions of PHP. Maybe add support for an initializer method or give
* recommendations that they should be set in the constructor of the BetterEnum subclass.
* PHP 5.6 constants cannot be defined conditionally, either, so having an initializer method
* would make a place for conditional definitions, too.
*/
abstract class BetterEnum implements JsonSerializable {
/** @const Name of the default value constant */
const __BETTER_ENUM_DEFAULT_KEY = '__default';
/** @var array|null */
private static $constCacheArray = null;
/** @var mixed|null */
private $value = null;
/**
* BetterEnum constructor
* @param mixed|null $initial_value
* @param bool $strict Provided for SplEnum compatibility (its purpose is unknown)
*/
public function __construct($initial_value = null, $strict = false) {
$enumClassName = get_called_class();
if ($initial_value === null) {
if (!self::isValidName(self::__BETTER_ENUM_DEFAULT_KEY)) {
throw new UnexpectedValueException('Default value not defined in enum ' . $enumClassName);
} else {
$validValue = self::getConstants()[self::__BETTER_ENUM_DEFAULT_KEY];
}
} elseif (!self::isValidValue($initial_value)) {
throw new UnexpectedValueException('Value not a const in enum ' . $enumClassName);
} else {
$validValue = $initial_value;
}
$this->value = $validValue;
}
/**
* @param string $name Name of the constant to validate
* @param bool $strict Case is significant when searching for name
* @return bool
*/
public static function isValidName($name, $strict = true) {
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$constantNames = array_map('strtoupper', array_keys($constants));
return in_array(strtoupper($name), $constantNames);
}
/**
* @param bool $includeDefault Include `__default` and its value. Included by default.
* @return array
*/
public static function getConstants($includeDefault = true) {
if (self::$constCacheArray === null) {
self::$constCacheArray = [];
}
$enumClassName = get_called_class();
if (!array_key_exists($enumClassName, self::$constCacheArray)) {
$reflect = new ReflectionClass($enumClassName);
self::$constCacheArray[$enumClassName] = $reflect->getConstants();
unset(self::$constCacheArray[$enumClassName]['__BETTER_ENUM_DEFAULT_KEY']);
}
$constants = self::$constCacheArray[$enumClassName];
if ($includeDefault === false) {
$constants = array_filter(
$constants,
function ($key) {
return $key !== self::__BETTER_ENUM_DEFAULT_KEY;
},
ARRAY_FILTER_USE_KEY);
}
return $constants;
}
/**
* @param mixed $value Value to validate
* @return bool
*/
public static function isValidValue($value) {
$constantValues = array_values(self::getConstants());
return in_array($value, $constantValues, $strict = true);
}
/**
* @deprecated 0.0.1 Provided for compatibility with SplEnum
* @see BetterEnum::getConstants()
* @param bool $include_default Include `__default` and its value. Not included by default.
* @return array
*/
public static function getConstList($include_default = false) {
return self::getConstants($include_default);
}
/**
* @return string String representation of the enum's value
*/
public function __toString() {
return strval($this->value);
}
/**
* @return mixed
*/
function jsonSerialize() {
return $this->getValue();
}
/**
* @return mixed
*/
public function getValue() {
return $this->value;
}
}
<?php
require_once 'BetterEnum.php';
class Month extends BetterEnum {
const __default = self::January;
const January = 1;
const February = 2;
const March = 3;
const April = 4;
const May = 5;
const June = 6;
const July = 7;
const August = 8;
const September = 9;
const October = 10;
const November = 11;
const December = 12;
}
echo new Month(Month::June) . PHP_EOL; // Output: 6
try {
new Month(13);
} catch (UnexpectedValueException $uve) {
echo $uve->getMessage() . PHP_EOL; // Output: Value not a const in enum Month
}
echo new Month() . PHP_EOL; // Output: 1
echo var_export(Month::getConstList(), true) . PHP_EOL; // Output: (array dump without `__default`)
echo var_export(Month::getConstants(), true) . PHP_EOL; // Output: (array dump with `__default`)
class ModelTColor extends BetterEnum {
const Black = 0;
}
$x = new ModelTColor(0);
echo $x . PHP_EOL; // Output: 0
try {
echo new ModelTColor() . PHP_EOL;
} catch (UnexpectedValueException $uve) {
echo $uve->getMessage() . PHP_EOL; // Output: Default value not defined in enum ModelTColor
}
class Number extends BetterEnum {
const
__default = null,
ONE = 1,
TWO = 2;
}
$x = new Number(2);
echo gettype($x) . PHP_EOL; // Output: object
echo gettype($x . '') . PHP_EOL; // Output: string
echo gettype($x->getValue()) . PHP_EOL; // Output: integer
echo Number::ONE . PHP_EOL; // Output: 1
echo var_export($x, true) . PHP_EOL; // Output: Number::__set_state(array( 'value' => 2,))
@aligoren
Copy link

aligoren commented Jan 7, 2017

Best solution :)

@marchrius
Copy link

Why not a generic name like AbstractEnum ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment