From 4382d1c6fed99d0a247229b50b6750d84bae3dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sat, 11 Sep 2021 11:11:21 +0200 Subject: [PATCH] Refactor code and cleanup --- src/BaseEnum.php | 364 +++++++++++---------- src/BaseEnumDefinition.php | 14 +- src/BaseEnumRoutable.php | 21 +- src/Exceptions/NotImplementedException.php | 5 + src/Exceptions/UnknownEnumProperty.php | 13 +- src/Rules/BaseEnumRule.php | 2 + src/Rules/EnumIsValidRule.php | 2 + src/Traits/CastsEnums.php | 4 +- 8 files changed, 236 insertions(+), 189 deletions(-) diff --git a/src/BaseEnum.php b/src/BaseEnum.php index 7c6f1f5..7bbef42 100644 --- a/src/BaseEnum.php +++ b/src/BaseEnum.php @@ -11,23 +11,89 @@ use Kwaadpepper\Enum\Exceptions\UnknownEnumProperty; use ReflectionClass; use TypeError; +/** + * This Base Enum is the class which should + * be extended to create an enumeration + */ abstract class BaseEnum implements JsonSerializable { + /** * @var string|int * @readonly */ protected $value; - /** @readonly */ + /** + * @var string + * @readonly + */ protected $label; - /** @var array> */ + /** @var array> */ private static $definitionCache = []; + + // -- ENUM MAGIC METHODS -- + /** - * @return array[BaseEnum] + * @param string $attribute + * @return int|string + * @throws UnknownEnumProperty */ + public function __get(string $attribute) + { + if ($attribute === 'value') { + return $this->value; + } + if ($attribute === 'label') { + return $this->label; + } + throw new UnknownEnumProperty($attribute); + } + + /** + * @param string $name + * @param array $arguments + * @return static + * @throws TypeError If anything else thant string or int is used. + * @throws BadMethodCallException If a matching definition cannot be found. + */ + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed + public static function __callStatic(string $name, array $arguments) + { + return static::make($name); + } + + /** + * @param string $name + * @param array $arguments + * @return bool|mixed + * @throws TypeError If anything else thant string or int is used. + * @throws BadMethodCallException If a matching definition cannot be found. + */ + public function __call(string $name, array $arguments) + { + if (strpos($name, 'is') === 0) { + $other = static::make(substr($name, 2)); + return $this->equals($other); + } + return self::__callStatic($name, $arguments); + } + + /** + * Output the value as a string + * @return string + */ + public function __toString(): string + { + return (string)$this->value; + } + + + // -- ENUM EXPORTATION METHODS -- + + /** @return array */ public static function toArray(): array { $array = []; @@ -37,9 +103,7 @@ abstract class BaseEnum implements JsonSerializable return $array; } - /** - * @return string[]|int[] - */ + /** @return array */ public static function toValues(): array { $array = []; @@ -49,9 +113,7 @@ abstract class BaseEnum implements JsonSerializable return $array; } - /** - * @return string[] - */ + /** @return array */ public static function toLabels(): array { $array = []; @@ -62,114 +124,20 @@ abstract class BaseEnum implements JsonSerializable } /** - * Get the enum definition + * Json serialize implementation. * - * @return mixed|null + * @return array */ - public function getDefinition() + public function jsonSerialize(): array { - $className = static::class; - foreach (self::$definitionCache[$className] as $definition => $enum) { - if ($enum->value === $this->value) { - return $definition; - } - } - return null; + return [ + 'label' => $this->label, + 'value' => $this->value + ]; } - /** - * @param string|int $value - * @throws TypeError - * @throws BadMethodCallException - * @return static - */ - public static function make($value): BaseEnum - { - if (!(is_string($value) || is_int($value))) { - $enumClass = static::class; - throw new TypeError("Only string and integer are allowed values for enum {$enumClass}."); - } - $definition = static::findDefinition($value); - - if ($definition === null) { - $enumClass = static::class; - throw new BadMethodCallException( - "There's no value {$value} defined for enum {$enumClass}," . - "consider adding it in the docblock definition." - ); - } - - $obj = new static(); - $obj->value = $definition->value; - $obj->label = $definition->label; - - return $obj; - } - - /** - * @param string $name - * - * @return int|string - * - * @throws UnknownEnumProperty - */ - public function __get(string $name) - { - if ($name === 'label') { - return $this->label; - } - - if ($name === 'value') { - return $this->value; - } - - throw new UnknownEnumProperty($name); - } - - /** - * @param string $name - * @param array $arguments - * - * @return static - */ - public static function __callStatic(string $name, array $arguments) - { - return static::make($name); - } - - /** - * @param string $name - * @param array $arguments - * - * @return bool|mixed - * - * @throws UnknownEnumMethod - */ - public function __call(string $name, array $arguments) - { - if (strpos($name, 'is') === 0) { - $other = static::make(substr($name, 2)); - return $this->equals($other); - } - - return self::__callStatic($name, $arguments); - } - - public function equals(BaseEnum ...$others): bool - { - foreach ($others as $other) { - // @phpcs:disable PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace - if ( - get_class($this) === get_class($other) - && $this->value === $other->value - ) { - return true; - } - } - - return false; - } + // -- DEFINITON METHODS -- /** * @return string[]|int[]|Closure @@ -190,90 +158,64 @@ abstract class BaseEnum implements JsonSerializable } /** - * @param string|int $input + * Get the enum definition * - * @return App\Enums\BaseEnumDefinition|null + * @return mixed|null */ - private static function findDefinition($input): ?BaseEnumDefinition + public function getDefinition() { - foreach (static::resolveDefinition() as $definition) { - if ($definition->equals($input)) { + $className = static::class; + foreach (self::$definitionCache[$className] as $definition => $enum) { + if ($enum->value === $this->value) { return $definition; } } - return null; } /** - * @return App\Enums\BaseEnumDefinition[] + * Parse a value to create its corresponding enumeration value. + * + * @param string|int $value + * @return static + * @throws TypeError If anything else thant string or int is used. + * @throws BadMethodCallException If a matching definition cannot be found. */ - private static function resolveDefinition(): array + public static function make($value): BaseEnum { - $className = static::class; - - if (self::$definitionCache[$className] ?? null) { - return self::$definitionCache[$className]; + if (!(is_string($value) || is_int($value))) { + $enumClass = static::class; + throw new TypeError("Only string and integer are allowed values for enum $enumClass."); } - $reflectionClass = new ReflectionClass($className); + $definition = static::findDefinition($value); - $docComment = $reflectionClass->getDocComment(); - - preg_match_all('/@method\s+static\s+self\s+([\w_]+)\(\)/', $docComment, $matches); - - $definition = []; - - $valueMap = static::values(); - - if ($valueMap instanceof Closure) { - $valueMap = array_map($valueMap, array_combine($matches[1], $matches[1])); + if ($definition === null) { + $enumClass = static::class; + throw new BadMethodCallException( + "There's no value $value defined for enum $enumClass, consider adding it in the docblock definition." + ); } - $labelMap = static::labels(); + $obj = new static(); + $obj->value = $definition->value; + $obj->label = $definition->label; - if ($labelMap instanceof Closure) { - $labelMap = array_map($labelMap, array_combine($matches[1], $matches[1])); - } - - foreach ($matches[1] as $methodName) { - $value = $valueMap[$methodName] = $valueMap[$methodName] ?? $methodName; - - $label = $labelMap[$methodName] = $labelMap[$methodName] ?? $methodName; - - $definition[$methodName] = new BaseEnumDefinition($methodName, $value, $label); - } - - if (self::arrayHasDuplicates($valueMap)) { - throw new DuplicateValuesException(); - } - - if (self::arrayHasDuplicates($labelMap)) { - throw new DuplicateLabelsException(); - } - - return self::$definitionCache[$className] = self::$definitionCache[$className] ?? $definition; + return $obj; } - private static function arrayHasDuplicates(array $array): bool + public function equals(BaseEnum ...$others): bool { - return count($array) > count(array_unique($array)); - } - - /** - * @return int|string - */ - public function jsonSerialize() - { - return [ - 'label' => $this->label, - 'value' => $this->value - ]; - } - - public function __toString(): string - { - return (string) $this->value; + foreach ($others as $other) { + // @phpcs:disable PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace + if ( + get_class($this) === get_class($other) + && $this->value === $other->value + ) { + return true; + } + } + return false; } /** @@ -286,4 +228,78 @@ abstract class BaseEnum implements JsonSerializable $cls = explode('\\', static::class); return array_pop($cls); } + + + // -- PRIVATE METHODS -- + + /** + * @param string|int $input + * @return \Kwaadpepper\Enum\BaseEnumDefinition|null + */ + private static function findDefinition($input): ?BaseEnumDefinition + { + foreach (static::resolveDefinition() as $definition) { + if ($definition->equals($input)) { + return $definition; + } + } + return null; + } + + /** + * @return \Kwaadpepper\Enum\BaseEnumDefinition[] + */ + private static function resolveDefinition(): array + { + if (isset(self::$definitionCache[static::class])) { + return self::$definitionCache[static::class]; + } + + $reflectionClass = new ReflectionClass(static::class); + $docComment = $reflectionClass->getDocComment(); + + preg_match_all('/@method\s+static\s+self\s+([\w_]+)\(\)/', $docComment, $matches); + + $definition = []; + $valueMap = static::values(); + $labelMap = static::labels(); + + if ($valueMap instanceof Closure) { + $valueMap = array_map($valueMap, array_combine($matches[1], $matches[1])); + } + if ($labelMap instanceof Closure) { + $labelMap = array_map($labelMap, array_combine($matches[1], $matches[1])); + } + + foreach ($matches[1] as $methodName) { + $valueMap[$methodName] = $valueMap[$methodName] ?? $methodName; + $labelMap[$methodName] = $labelMap[$methodName] ?? $methodName; + $definition[$methodName] = new BaseEnumDefinition( + $methodName, + $valueMap[$methodName], + $labelMap[$methodName] + ); + } + + if (self::arrayHasDuplicates($valueMap)) { + throw new DuplicateValuesException(); + } + + if (self::arrayHasDuplicates($labelMap)) { + throw new DuplicateLabelsException(); + } + + return self::$definitionCache[static::class] = $definition; + } + + /** + * Check if an array has duplicated values + * + * @param array $array + * @return boolean True if it has duplicated value. + */ + private static function arrayHasDuplicates(array $array): bool + { + return count($array) > count(array_unique($array)); + } } diff --git a/src/BaseEnumDefinition.php b/src/BaseEnumDefinition.php index 8cb569f..e07d377 100644 --- a/src/BaseEnumDefinition.php +++ b/src/BaseEnumDefinition.php @@ -2,8 +2,13 @@ namespace Kwaadpepper\Enum; +/** + * The definition of an enumeration + * This defines it's value, label and method name + */ class BaseEnumDefinition { + /** @var string|int */ public $value; @@ -21,13 +26,12 @@ class BaseEnumDefinition public function __construct(string $methodName, $value, string $label) { $this->methodName = strtolower($methodName); - $this->value = $value; - $this->label = $label; + $this->value = $value; + $this->label = $label; } /** * @param string|int $input - * * @return bool */ public function equals($input): bool @@ -35,11 +39,9 @@ class BaseEnumDefinition if ($this->value === $input) { return true; } - - if (is_string($input) && $this->methodName === strtolower($input)) { + if (is_string($input) and $this->methodName === strtolower($input)) { return true; } - return false; } } diff --git a/src/BaseEnumRoutable.php b/src/BaseEnumRoutable.php index a4ea8a2..4ffa946 100644 --- a/src/BaseEnumRoutable.php +++ b/src/BaseEnumRoutable.php @@ -6,6 +6,12 @@ use BadMethodCallException; use Illuminate\Contracts\Routing\UrlRoutable; use Kwaadpepper\Enum\Exceptions\NotImplementedException; +/** + * This Base Enum will be routable with laravel. + * So you can use it with service container passing an + * enumeration as any route parameter directly as + * a controller parameter. + */ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable { /** @@ -35,20 +41,21 @@ abstract class BaseEnumRoutable extends BaseEnum implements UrlRoutable * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed public function resolveRouteBinding($value, $field = null) { // try to cast numeric value first try { return static::make(is_numeric($value) ? intval($value) : $value); } catch (BadMethodCallException $e) { + // try string value after that + try { + return static::make($value); + } catch (BadMethodCallException $e) { + // could not find a suitable value + return null; + } } - // try string value after that - try { - return static::make($value); - } catch (BadMethodCallException $e) { - } - // could not find a suitable value - return null; } /** diff --git a/src/Exceptions/NotImplementedException.php b/src/Exceptions/NotImplementedException.php index ae8e278..1ca37db 100644 --- a/src/Exceptions/NotImplementedException.php +++ b/src/Exceptions/NotImplementedException.php @@ -4,6 +4,11 @@ namespace Kwaadpepper\Enum\Exceptions; use Exception; +/** + * This exception is used only + * as a safe guard it should not be thrown + * at any moment + */ class NotImplementedException extends Exception { // diff --git a/src/Exceptions/UnknownEnumProperty.php b/src/Exceptions/UnknownEnumProperty.php index 8ac2b8b..661db7b 100644 --- a/src/Exceptions/UnknownEnumProperty.php +++ b/src/Exceptions/UnknownEnumProperty.php @@ -4,7 +4,18 @@ namespace Kwaadpepper\Enum\Exceptions; use Exception; +/** + * Is thrown when accessing other propety than + * label or value on a enumeration. + */ class UnknownEnumProperty extends Exception { - // + + protected $message; + + public function __construct(string $attribute) + { + $this->message = "Unknown attribute $attribute, enumeration only have propeties `label` and `value`."; + parent::__construct(); + } } diff --git a/src/Rules/BaseEnumRule.php b/src/Rules/BaseEnumRule.php index fc33387..9bd6824 100644 --- a/src/Rules/BaseEnumRule.php +++ b/src/Rules/BaseEnumRule.php @@ -7,6 +7,7 @@ use Kwaadpepper\Enum\Exceptions\UnknownEnumClass; abstract class BaseEnumRule implements Rule { + /** @var string $enum */ protected $enumClass; @@ -33,6 +34,7 @@ abstract class BaseEnumRule implements Rule * @return bool */ abstract public function passes($attribute, $value): bool; + /** * Get the validation error message. * diff --git a/src/Rules/EnumIsValidRule.php b/src/Rules/EnumIsValidRule.php index c4e6675..c3f36b3 100644 --- a/src/Rules/EnumIsValidRule.php +++ b/src/Rules/EnumIsValidRule.php @@ -7,6 +7,7 @@ use TypeError; class EnumIsValidRule extends BaseEnumRule { + private $messageType = 0; /** @@ -16,6 +17,7 @@ class EnumIsValidRule extends BaseEnumRule * @param mixed $value * @return bool */ + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed public function passes($attribute, $value): bool { try { diff --git a/src/Traits/CastsEnums.php b/src/Traits/CastsEnums.php index ff7c45f..ac72907 100644 --- a/src/Traits/CastsEnums.php +++ b/src/Traits/CastsEnums.php @@ -46,7 +46,9 @@ trait CastsEnums $value = $this->castAttribute($key, $value); } // try cast to int if is int - $value = is_numeric($value) && floatval(intval($value)) === floatval($value) ? (int)$value : $value; + $value = is_numeric($value) && floatval(intval($value)) === floatval($value) ? + (int)$value : $value; + $this->attributes[$key] = $enum::make($value)->value; }