242 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
declare(strict_types = 1);
 | 
						|
 | 
						|
namespace DASPRiD\Enum;
 | 
						|
 | 
						|
use DASPRiD\Enum\Exception\CloneNotSupportedException;
 | 
						|
use DASPRiD\Enum\Exception\IllegalArgumentException;
 | 
						|
use DASPRiD\Enum\Exception\MismatchException;
 | 
						|
use DASPRiD\Enum\Exception\SerializeNotSupportedException;
 | 
						|
use DASPRiD\Enum\Exception\UnserializeNotSupportedException;
 | 
						|
use ReflectionClass;
 | 
						|
 | 
						|
abstract class AbstractEnum
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * @var string
 | 
						|
     */
 | 
						|
    private $name;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var int
 | 
						|
     */
 | 
						|
    private $ordinal;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var array<string, array<string, static>>
 | 
						|
     */
 | 
						|
    private static $values = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var array<string, bool>
 | 
						|
     */
 | 
						|
    private static $allValuesLoaded = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var array<string, array>
 | 
						|
     */
 | 
						|
    private static $constants = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * The constructor is private by default to avoid arbitrary enum creation.
 | 
						|
     *
 | 
						|
     * When creating your own constructor for a parameterized enum, make sure to declare it as protected, so that
 | 
						|
     * the static methods are able to construct it. Avoid making it public, as that would allow creation of
 | 
						|
     * non-singleton enum instances.
 | 
						|
     */
 | 
						|
    private function __construct()
 | 
						|
    {
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Magic getter which forwards all calls to {@see self::valueOf()}.
 | 
						|
     *
 | 
						|
     * @return static
 | 
						|
     */
 | 
						|
    final public static function __callStatic(string $name, array $arguments) : self
 | 
						|
    {
 | 
						|
        return static::valueOf($name);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns an enum with the specified name.
 | 
						|
     *
 | 
						|
     * The name must match exactly an identifier used to declare an enum in this type (extraneous whitespace characters
 | 
						|
     * are not permitted).
 | 
						|
     *
 | 
						|
     * @return static
 | 
						|
     * @throws IllegalArgumentException if the enum has no constant with the specified name
 | 
						|
     */
 | 
						|
    final public static function valueOf(string $name) : self
 | 
						|
    {
 | 
						|
        if (isset(self::$values[static::class][$name])) {
 | 
						|
            return self::$values[static::class][$name];
 | 
						|
        }
 | 
						|
 | 
						|
        $constants = self::constants();
 | 
						|
 | 
						|
        if (array_key_exists($name, $constants)) {
 | 
						|
            return self::createValue($name, $constants[$name][0], $constants[$name][1]);
 | 
						|
        }
 | 
						|
 | 
						|
        throw new IllegalArgumentException(sprintf('No enum constant %s::%s', static::class, $name));
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @return static
 | 
						|
     */
 | 
						|
    private static function createValue(string $name, int $ordinal, array $arguments) : self
 | 
						|
    {
 | 
						|
        $instance = new static(...$arguments);
 | 
						|
        $instance->name = $name;
 | 
						|
        $instance->ordinal = $ordinal;
 | 
						|
        self::$values[static::class][$name] = $instance;
 | 
						|
        return $instance;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Obtains all possible types defined by this enum.
 | 
						|
     *
 | 
						|
     * @return static[]
 | 
						|
     */
 | 
						|
    final public static function values() : array
 | 
						|
    {
 | 
						|
        if (isset(self::$allValuesLoaded[static::class])) {
 | 
						|
            return self::$values[static::class];
 | 
						|
        }
 | 
						|
 | 
						|
        if (! isset(self::$values[static::class])) {
 | 
						|
            self::$values[static::class] = [];
 | 
						|
        }
 | 
						|
 | 
						|
        foreach (self::constants() as $name => $constant) {
 | 
						|
            if (array_key_exists($name, self::$values[static::class])) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            static::createValue($name, $constant[0], $constant[1]);
 | 
						|
        }
 | 
						|
 | 
						|
        uasort(self::$values[static::class], function (self $a, self $b) {
 | 
						|
            return $a->ordinal() <=> $b->ordinal();
 | 
						|
        });
 | 
						|
 | 
						|
        self::$allValuesLoaded[static::class] = true;
 | 
						|
        return self::$values[static::class];
 | 
						|
    }
 | 
						|
 | 
						|
    private static function constants() : array
 | 
						|
    {
 | 
						|
        if (isset(self::$constants[static::class])) {
 | 
						|
            return self::$constants[static::class];
 | 
						|
        }
 | 
						|
 | 
						|
        self::$constants[static::class] = [];
 | 
						|
        $reflectionClass = new ReflectionClass(static::class);
 | 
						|
        $ordinal = -1;
 | 
						|
 | 
						|
        foreach ($reflectionClass->getReflectionConstants() as $reflectionConstant) {
 | 
						|
            if (! $reflectionConstant->isProtected()) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            $value = $reflectionConstant->getValue();
 | 
						|
 | 
						|
            self::$constants[static::class][$reflectionConstant->name] = [
 | 
						|
                ++$ordinal,
 | 
						|
                is_array($value) ? $value : []
 | 
						|
            ];
 | 
						|
        }
 | 
						|
 | 
						|
        return self::$constants[static::class];
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the name of this enum constant, exactly as declared in its enum declaration.
 | 
						|
     *
 | 
						|
     * Most programmers should use the {@see self::__toString()} method in preference to this one, as the toString
 | 
						|
     * method may return a more user-friendly name. This method is designed primarily for use in specialized situations
 | 
						|
     * where correctness depends on getting the exact name, which will not vary from release to release.
 | 
						|
     */
 | 
						|
    final public function name() : string
 | 
						|
    {
 | 
						|
        return $this->name;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial
 | 
						|
     * constant is assigned an ordinal of zero).
 | 
						|
     *
 | 
						|
     * Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data
 | 
						|
     * structures.
 | 
						|
     */
 | 
						|
    final public function ordinal() : int
 | 
						|
    {
 | 
						|
        return $this->ordinal;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Compares this enum with the specified object for order.
 | 
						|
     *
 | 
						|
     * Returns negative integer, zero or positive integer as this object is less than, equal to or greater than the
 | 
						|
     * specified object.
 | 
						|
     *
 | 
						|
     * Enums are only comparable to other enums of the same type. The natural order implemented by this method is the
 | 
						|
     * order in which the constants are declared.
 | 
						|
     *
 | 
						|
     * @throws MismatchException if the passed enum is not of the same type
 | 
						|
     */
 | 
						|
    final public function compareTo(self $other) : int
 | 
						|
    {
 | 
						|
        if (! $other instanceof static) {
 | 
						|
            throw new MismatchException(sprintf(
 | 
						|
                'The passed enum %s is not of the same type as %s',
 | 
						|
                get_class($other),
 | 
						|
                static::class
 | 
						|
            ));
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->ordinal - $other->ordinal;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Forbid cloning enums.
 | 
						|
     *
 | 
						|
     * @throws CloneNotSupportedException
 | 
						|
     */
 | 
						|
    final public function __clone()
 | 
						|
    {
 | 
						|
        throw new CloneNotSupportedException();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Forbid serializing enums.
 | 
						|
     *
 | 
						|
     * @throws SerializeNotSupportedException
 | 
						|
     */
 | 
						|
    final public function __sleep() : array
 | 
						|
    {
 | 
						|
        throw new SerializeNotSupportedException();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Forbid unserializing enums.
 | 
						|
     *
 | 
						|
     * @throws UnserializeNotSupportedException
 | 
						|
     */
 | 
						|
    final public function __wakeup() : void
 | 
						|
    {
 | 
						|
        throw new UnserializeNotSupportedException();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Turns the enum into a string representation.
 | 
						|
     *
 | 
						|
     * You may override this method to give a more user-friendly version.
 | 
						|
     */
 | 
						|
    public function __toString() : string
 | 
						|
    {
 | 
						|
        return $this->name;
 | 
						|
    }
 | 
						|
}
 |