Abstract base class for enum-like behavior in PHP 7.4.
Description
This class provides enum-like functionality for PHP versions that don’t support native enums.
Child classes should define uppercase snake_case constants for enum values.
Methods
| Name | Description |
|---|---|
| AbstractEnum::__call | Handles dynamic method calls for enum checking. |
| AbstractEnum::__callStatic | Handles static method calls for enum creation. |
| AbstractEnum::__construct | Constructor is private to ensure instances are created through static methods. |
| AbstractEnum::__get | Provides read-only access to properties. |
| AbstractEnum::__set | Prevents property modification. |
| AbstractEnum::__toString | Returns string representation of the enum. |
| AbstractEnum::camelCaseToConstant | Converts camelCase to CONSTANT_CASE. |
| AbstractEnum::cases | Gets all enum cases. |
| AbstractEnum::determineClassEnumerations | Determines the class enumerations by reflecting on class constants. |
| AbstractEnum::equals | Checks if this enum has the same value as the given value. |
| AbstractEnum::from | Creates an enum instance from a value, throws exception if invalid. |
| AbstractEnum::getConstants | Gets all constants for this enum class. |
| AbstractEnum::getInstance | Gets or creates a singleton instance for the given value and name. |
| AbstractEnum::getValues | Gets all valid values for this enum. |
| AbstractEnum::is | Checks if this enum is the same instance type and value as another enum. |
| AbstractEnum::isValidValue | Checks if a value is valid for this enum. |
| AbstractEnum::jsonSerialize | Converts the enum to a JSON-serializable format. |
| AbstractEnum::tryFrom | Tries to create an enum instance from a value, returns null if invalid. |
Source
abstract class AbstractEnum implements JsonSerializable
{
/**
* @var string The value of the enum instance.
*/
private string $value;
/**
* @var string The name of the enum constant.
*/
private string $name;
/**
* @var array<string, array<string, string>> Cache for reflection data.
*/
private static array $cache = [];
/**
* @var array<string, array<string, self>> Cache for enum instances.
*/
private static array $instances = [];
/**
* Constructor is private to ensure instances are created through static methods.
*
* @since 0.1.0
*
* @param string $value The enum value.
* @param string $name The constant name.
*/
final private function __construct(string $value, string $name)
{
$this->value = $value;
$this->name = $name;
}
/**
* Provides read-only access to properties.
*
* @since 0.1.0
*
* @param string $property The property name.
* @return mixed The property value.
* @throws BadMethodCallException If property doesn't exist.
*/
final public function __get(string $property)
{
if ($property === 'value' || $property === 'name') {
return $this->{$property};
}
throw new BadMethodCallException(sprintf('Property %s::%s does not exist', static::class, $property));
}
/**
* Prevents property modification.
*
* @since 0.1.0
*
* @param string $property The property name.
* @param mixed $value The value to set.
* @throws BadMethodCallException Always, as enum properties are read-only.
*/
final public function __set(string $property, $value): void
{
throw new BadMethodCallException(sprintf('Cannot modify property %s::%s - enum properties are read-only', static::class, $property));
}
/**
* Creates an enum instance from a value, throws exception if invalid.
*
* @since 0.1.0
*
* @param string $value The enum value.
* @return static The enum instance.
* @throws InvalidArgumentException If the value is not valid.
*/
final public static function from(string $value): self
{
$instance = self::tryFrom($value);
if ($instance === null) {
throw new InvalidArgumentException(sprintf('%s is not a valid backing value for enum %s', $value, static::class));
}
return $instance;
}
/**
* Tries to create an enum instance from a value, returns null if invalid.
*
* @since 0.1.0
*
* @param string $value The enum value.
* @return static|null The enum instance or null.
*/
final public static function tryFrom(string $value): ?self
{
$constants = static::getConstants();
foreach ($constants as $name => $constantValue) {
if ($constantValue === $value) {
return self::getInstance($constantValue, $name);
}
}
return null;
}
/**
* Gets all enum cases.
*
* @since 0.1.0
*
* @return static[] Array of all enum instances.
*/
final public static function cases(): array
{
$cases = [];
$constants = static::getConstants();
foreach ($constants as $name => $value) {
$cases[] = self::getInstance($value, $name);
}
return $cases;
}
/**
* Checks if this enum has the same value as the given value.
*
* @since 0.1.0
*
* @param string|self $other The value or enum to compare.
* @return bool True if values are equal.
*/
final public function equals($other): bool
{
if ($other instanceof self) {
return $this->is($other);
}
return $this->value === $other;
}
/**
* Checks if this enum is the same instance type and value as another enum.
*
* @since 0.1.0
*
* @param self $other The other enum to compare.
* @return bool True if enums are identical.
*/
final public function is(self $other): bool
{
return $this === $other;
// Since we're using singletons, we can use identity comparison
}
/**
* Gets all valid values for this enum.
*
* @since 0.1.0
*
* @return string[] List of all enum values.
*/
final public static function getValues(): array
{
return array_values(static::getConstants());
}
/**
* Checks if a value is valid for this enum.
*
* @since 0.1.0
*
* @param string $value The value to check.
* @return bool True if value is valid.
*/
final public static function isValidValue(string $value): bool
{
return in_array($value, self::getValues(), \true);
}
/**
* Gets or creates a singleton instance for the given value and name.
*
* @since 0.1.0
*
* @param string $value The enum value.
* @param string $name The constant name.
* @return static The enum instance.
*/
private static function getInstance(string $value, string $name): self
{
$className = static::class;
if (!isset(self::$instances[$className])) {
self::$instances[$className] = [];
}
if (!isset(self::$instances[$className][$name])) {
$instance = new $className($value, $name);
self::$instances[$className][$name] = $instance;
}
/** @var static */
return self::$instances[$className][$name];
}
/**
* Gets all constants for this enum class.
*
* @since 0.1.0
*
* @return array<string, string> Map of constant names to values.
* @throws RuntimeException If invalid constant found.
*/
final protected static function getConstants(): array
{
$className = static::class;
if (!isset(self::$cache[$className])) {
self::$cache[$className] = static::determineClassEnumerations($className);
}
return self::$cache[$className];
}
/**
* Determines the class enumerations by reflecting on class constants.
*
* This method can be overridden by subclasses to customize how
* enumerations are determined (e.g., to add dynamic constants).
*
* @since 0.1.0
*
* @param class-string $className The fully qualified class name.
* @return array<string, string> Map of constant names to values.
* @throws RuntimeException If invalid constant found.
*/
protected static function determineClassEnumerations(string $className): array
{
$reflection = new ReflectionClass($className);
$constants = $reflection->getConstants();
// Validate all constants
$enumConstants = [];
foreach ($constants as $name => $value) {
// Check if constant name follows uppercase snake_case pattern
if (!preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) {
throw new RuntimeException(sprintf('Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.', $name, $className));
}
// Check if value is valid type
if (!is_string($value)) {
throw new RuntimeException(sprintf('Invalid enum value type for constant %s::%s. ' . 'Only string values are allowed, %s given.', $className, $name, gettype($value)));
}
$enumConstants[$name] = $value;
}
return $enumConstants;
}
/**
* Handles dynamic method calls for enum checking.
*
* @since 0.1.0
*
* @param string $name The method name.
* @param array<mixed> $arguments The method arguments.
* @return bool True if the enum value matches.
* @throws BadMethodCallException If the method doesn't exist.
*/
final public function __call(string $name, array $arguments): bool
{
// Handle is* methods
if (str_starts_with($name, 'is')) {
$constantName = self::camelCaseToConstant(substr($name, 2));
$constants = static::getConstants();
if (isset($constants[$constantName])) {
return $this->value === $constants[$constantName];
}
}
throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name));
}
/**
* Handles static method calls for enum creation.
*
* @since 0.1.0
*
* @param string $name The method name.
* @param array<mixed> $arguments The method arguments.
* @return static The enum instance.
* @throws BadMethodCallException If the method doesn't exist.
*/
final public static function __callStatic(string $name, array $arguments): self
{
$constantName = self::camelCaseToConstant($name);
$constants = static::getConstants();
if (isset($constants[$constantName])) {
return self::getInstance($constants[$constantName], $constantName);
}
throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name));
}
/**
* Converts camelCase to CONSTANT_CASE.
*
* @since 0.1.0
*
* @param string $camelCase The camelCase string.
* @return string The CONSTANT_CASE version.
*/
private static function camelCaseToConstant(string $camelCase): string
{
$snakeCase = preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase);
if ($snakeCase === null) {
return strtoupper($camelCase);
}
return strtoupper($snakeCase);
}
/**
* Returns string representation of the enum.
*
* @since 0.1.0
*
* @return string The enum value.
*/
final public function __toString(): string
{
return $this->value;
}
/**
* Converts the enum to a JSON-serializable format.
*
* @since 0.1.0
*
* @return string The enum value.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->value;
}
}
Changelog
| Version | Description |
|---|---|
| 0.1.0 | Introduced. |
User Contributed Notes
You must log in before being able to contribute a note or feedback.