From 78292f0bf3d681957bf3199fc3849787a6069996 Mon Sep 17 00:00:00 2001 From: Julien Rosset Date: Mon, 16 Dec 2024 11:46:44 +0100 Subject: [PATCH] Add getPropertyAsEnum method --- composer.json | 8 +- src/EnvReader/GenericConfig.php | 102 ++++++++++++++++++------ src/EnvReader/TMultiLevelProperties.php | 4 +- tests/.env | 4 +- tests/TestEnum.php | 7 ++ tests/test_env.php | 4 +- 6 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 tests/TestEnum.php diff --git a/composer.json b/composer.json index 504609a..01efdb1 100644 --- a/composer.json +++ b/composer.json @@ -9,15 +9,15 @@ }, "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "3.x-dev" } }, "minimum-stability": "stable", "require": { - "php": "^7.4 || ^8.0", - "jrosset/singleton": "^1.5", - "jrosset/collections": "^2.3 || ^3.0" + "php": "^8.1", + "jrosset/singleton": "^2.0", + "jrosset/collections": "^3.5" }, "autoload": { "psr-4": { diff --git a/src/EnvReader/GenericConfig.php b/src/EnvReader/GenericConfig.php index cc10bd5..209b63c 100644 --- a/src/EnvReader/GenericConfig.php +++ b/src/EnvReader/GenericConfig.php @@ -5,13 +5,18 @@ namespace jrosset\EnvReader; use DateTime; use DateTimeInterface; use Exception; +use InvalidArgumentException; use jrosset\Collections\IArrayCast; use jrosset\Collections\InsensitiveCaseKeyCollection; use jrosset\Collections\InsensitiveCaseKeyImmutableCollection; use jrosset\Singleton\ISingleton; use jrosset\Singleton\TSingleton; use RangeException; +use ReflectionEnum; +use ReflectionEnumBackedCase; +use ReflectionException; use UnexpectedValueException; +use UnitEnum; /** * A generic configuration class @@ -40,10 +45,8 @@ abstract class GenericConfig implements ISingleton { * Initial properties * * @return string[]|IArrayCast Initial properties - * - * @noinspection PhpReturnDocTypeMismatchInspection */ - protected function initialProperties () { + protected function initialProperties (): array|IArrayCast { return []; } /** @@ -76,15 +79,15 @@ abstract class GenericConfig implements ISingleton { /** * Get a property value * - * @param string $name The property name - * @param mixed $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + * @param string $name The property name + * @param mixed|null $default The default value if property is not set + *
Raise an exception if property is not set AND $default is Null * * @return mixed The property value * * @throws UnexpectedValueException If property is not set AND $default is Null */ - public function getProperty (string $name, $default = null) { + public function getProperty (string $name, mixed $default = null): mixed { if (!$this->hasProperty($name)) { if ($default === null) { throw new UnexpectedValueException('The "' . $name . '" property is not set'); @@ -100,7 +103,7 @@ abstract class GenericConfig implements ISingleton { * * @param string $name The property name * @param string|null $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is not set AND $default is Null * * @return string The property value * @@ -122,7 +125,7 @@ abstract class GenericConfig implements ISingleton { * * @param string $name The property name * @param bool|null $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is not set AND $default is Null * * @return bool The property value * @@ -132,25 +135,18 @@ abstract class GenericConfig implements ISingleton { * @noinspection PhpUnused */ public function getPropertyAsBool (string $name, ?bool $default = null): bool { - switch ($value = $this->getProperty($name, $default === null ? null : ($default === true ? '1' : '0'))) { - case '1': - case 'true': - return true; - - case '0': - case 'false': - return false; - - default: - throw new RangeException('The "' . $name . '" property is not a valid boolean : ' . $value); - } + return match ($value = $this->getProperty($name, $default === null ? null : ($default === true ? '1' : '0'))) { + '1', 'true' => true, + '0', 'false' => false, + default => throw new RangeException('The "' . $name . '" property is not a valid boolean : ' . $value), + }; } /** * Get a property value as an integer * * @param string $name The property name * @param int|null $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is not set AND $default is Null * * @return int The property value * @@ -171,7 +167,7 @@ abstract class GenericConfig implements ISingleton { * * @param string $name The property name * @param float|null $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is not set AND $default is Null * * @return float The property value * @@ -194,7 +190,7 @@ abstract class GenericConfig implements ISingleton { * * @param string $name The property name * @param DateTime|null $default The default value if property is not set - * Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is not set AND $default is Null * * @return DateTime The property value * @@ -206,7 +202,7 @@ abstract class GenericConfig implements ISingleton { public function getPropertyAsDateTime (string $name, ?DateTime $default = null): DateTime { $value = DateTime::createFromFormat( DateTimeInterface::RFC3339, - $this->getProperty($name, $default === null ? null : $default->format(DateTimeInterface::RFC3339)) + $this->getProperty($name, $default?->format(DateTimeInterface::RFC3339)) ); $errors = DateTime::getLastErrors(); @@ -234,4 +230,60 @@ abstract class GenericConfig implements ISingleton { return $value; } + /** + * Get a property value as an enum + * + * @param string $name The property name + * @param class-string $enumClass The enum class name + * @param UnitEnum|null $default The default value if property is not set + *
Raise an exception if not $enumClass enum + *
Raise an exception if property is not set AND $default is Null + *
Raise an exception if property is set but not valid (not an enum) + * + * @return UnitEnum The enum + * + * @throws ReflectionException If $enumClass is not a valid enum class + * @throws InvalidArgumentException If $default is not a $enumClass enum + */ + public function getPropertyAsEnum (string $name, string $enumClass, ?UnitEnum $default = null): UnitEnum { + $enumReflection = new ReflectionEnum($enumClass); + + //region Check default value type + if ($default !== null && !$default instanceof $enumClass) { + throw new InvalidArgumentException('The default property type must be an ' . $enumClass); + } + //endregion + //region Return default value (or raise exception) if property is not set + if (!$this->hasProperty($name)) { + if ($default === null) { + throw new UnexpectedValueException('The "' . $name . '" property is not set'); + } + return $default; + } + //endregion + + //region Try to find the enum through a case name + $propertyValue = $this->getPropertyAsString($name); + if ($enumReflection->hasCase($propertyValue)) { + return $enumReflection->getCase($propertyValue)->getValue(); + } + //endregion + //region Try to find enum through the value (if BackedEnum) + if ($enumReflection->isBacked()) { + if (((string)$enumReflection->getBackingType()) === 'int') { + $propertyValue = $this->getPropertyAsInt($name); + } + + /** @var ReflectionEnumBackedCase $enumBackedCase */ + foreach ($enumReflection->getCases() as $enumBackedCase) { + if ($enumBackedCase->getBackingValue() === $propertyValue) { + return $enumBackedCase->getValue(); + } + } + } + //endregion + //region Unable to find valid enum → raise exception + throw new UnexpectedValueException('The "' . $name . '" property is not a valid enum ' . $enumClass); + //endregion + } } \ No newline at end of file diff --git a/src/EnvReader/TMultiLevelProperties.php b/src/EnvReader/TMultiLevelProperties.php index e284ed1..c43c293 100644 --- a/src/EnvReader/TMultiLevelProperties.php +++ b/src/EnvReader/TMultiLevelProperties.php @@ -43,14 +43,14 @@ trait TMultiLevelProperties { * Get a property value * * @param string|string[]|IArrayCast $name The property name - * @param mixed $default The default value if property is not set + * @param mixed|null $default The default value if property is not set * Raise an exception if property is not set AND $default is Null * * @return mixed The property value * * @throws UnexpectedValueException If property is not set AND $default is Null */ - public function getProperty ($name, $default = null) { + public function getProperty ($name, mixed $default = null) { if (!$this->hasProperty($name)) { if ($default === null) { throw new UnexpectedValueException('The "' . $name . '" property is not set'); diff --git a/tests/.env b/tests/.env index 53f4747..e382a12 100644 --- a/tests/.env +++ b/tests/.env @@ -2,4 +2,6 @@ WEBSITE_NAME="Foo Bar" DB_NAME=Bar DB_LOGIN=Bar -DB_PASS=***** \ No newline at end of file +DB_PASS=***** + +TEST=Test2 \ No newline at end of file diff --git a/tests/TestEnum.php b/tests/TestEnum.php new file mode 100644 index 0000000..c87151d --- /dev/null +++ b/tests/TestEnum.php @@ -0,0 +1,7 @@ +getProperties()); -var_dump(Env::getInstance()->getProperty('db_host', 'localhost')); \ No newline at end of file +var_dump(Env::getInstance()->getProperty('db_host', 'localhost')); +var_dump(Env::getInstance()->getPropertyAsEnum('test', TestEnum::class, TestEnum::Test1)); \ No newline at end of file