Skip to content

Instantly share code, notes, and snippets.

@FerrielMelarpis
Created January 7, 2019 02:52
Show Gist options
  • Save FerrielMelarpis/b0209e57f0c3302afc8e650a902b85f8 to your computer and use it in GitHub Desktop.
Save FerrielMelarpis/b0209e57f0c3302afc8e650a902b85f8 to your computer and use it in GitHub Desktop.
PHP Enum
<?php
class InvalidValueException extends InvalidArgumentException {
public function __construct( $value, $class ) {
$message = sprintf( '%s is not an acceptable value for "%s" enum.', json_encode( $value ), $class );
parent::__construct( $message );
}
}
interface EnumInterface {
/**
* @param int|string $value The value of a particular enumerated constant
*
* @throws InvalidValueException When $value is not acceptable for this enumeration type
*
* @return EnumInterface The enum instance for given value
*/
public static function get( $value ) : self;
/**
* Returns any possible value for the enumeration.
*
* @return int[]|string[]
*/
public static function values() : array;
/**
* @param int|string $value
*
* @return bool True if the value is acceptable for this enumeration
*/
public static function accepts( $value ) : bool;
/**
* Returns the list of all possible enum instances.
*
* @return EnumInterface[]
*/
public static function instances() : array;
/**
* Gets the raw value.
*
* @return int|string
*/
public function getValue();
// Determines whether two enumerations instances should be considered the same.
public function equals( self $enum ) : bool;
/**
* Determines if the enumeration instance value is equal to the given value.
*
* @param int|string $value
*
* @return bool
*/
public function is( $value ) : bool;
}
abstract class Enum implements EnumInterface {
/**
* Cached array of enum instances by enum type (FQCN).
* This cache is used in order to make single enums values act as singletons.
* This means you'll always get the exact same instance for a same enum value.
*
* @var array
*/
private static $instances;
/** @var mixed */
protected $value;
/**
* The constructor is private and cannot be overridden: use the static get method instead.
*
* @param mixed $value The raw value of an enumeration
*/
final private function __construct( $value ) {
$this->value = $value;
$enumType = static::class;
$identifier = serialize( $value );
if( isset( self::$instances[ $enumType ][ $identifier ] ) ) {
throw new LogicException(
'"__construct" should not be called when an instance already exists for this enum value.'
);
}
if( ! isset( self::$instances[ $enumType ] ) ) {
self::$instances[ $enumType ] = [];
}
self::$instances[ $enumType ][ $identifier ] = $this;
}
public static function get( $value ) : EnumInterface {
// Return the cached instance for given value if it already exists:
$instance = self::getCachedInstance( $value );
if( isset( $instance ) ) {
return $instance;
}
if( ! static::accepts( $value ) ) {
throw new InvalidValueException( $value, static::class );
}
return new static( $value );
}
/**
* Instantiates a new enumeration.
*
* @param string $name The name of a particular enumerated constant
* @param array $arguments
*
* @throws \BadMethodCallException On invalid constant name
*
* @return static When $name is an existing constant for this enumeration type
*/
public static function __callStatic( $name, $arguments = [] ) : EnumInterface {
if( ! \defined( 'static::' . $name ) ) {
throw new \BadMethodCallException( sprintf(
'No constant named "%s" exists in class "%s"',
$name,
static::class
) );
}
return static::get( \constant( 'static::' . $name ) );
}
/**
* {@inheritdoc}
*/
public static function accepts( $value ) : bool {
return \in_array( $value, static::values(), true );
}
/**
* {@inheritdoc}
*/
public static function instances() : array {
return array_map( function( $value ) {
return static::get( $value );
}, static::values() );
}
private static function getCachedInstance( $value ) {
$enumType = static::class;
$identifier = serialize( $value );
return self::$instances[ $enumType ][ $identifier ] ?? null;
}
/**
* {@inheritdoc}
*/
public function getValue() {
return $this->value;
}
/**
* {@inheritdoc}
*/
public function equals( self $enum ) : bool {
return \get_class( $this ) === \get_class( $enum ) && $this->value === $enum->getValue();
}
/**
* {@inheritdoc}
*/
public function is( $value ) : bool {
return $this->getValue() === $value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment