diff --git a/CHANGELOG.md b/CHANGELOG.md index 91c288a..50673ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `Enumhancer` will be documented in this file +## 1.19.0 - 2022-12-15 + +- You can now use constants for [Mappers](docs/mappers) + and [Defaults](docs/defaults.md) +- you can now flag a unit enum as `strict`, so you don't + have to worry about casing in [Values](docs/value.md). + ## 1.18.0 - 2022-12-14 - Added Magic method functionality to [State](docs/state.md) diff --git a/docs/defaults.md b/docs/defaults.md index e51322e..5f235b3 100644 --- a/docs/defaults.md +++ b/docs/defaults.md @@ -23,6 +23,15 @@ enum YourDefaultEnum { case Default; } +enum MyDefaultConstEnum { + use Defaults, Makers; + + case MyEnum; + case MyDefaultEnum; + + const Default = MyDefaultEnum::MyDefaultEnum; +} + enum MyDefaultEnum { use Defaults, Makers; @@ -54,6 +63,14 @@ YourDefaultEnum::get('unknown'); // crashes YourDefaultEnum::tryGet('default'); // returns YourDefaultEnum::Default YourDefaultEnum::tryGet('unknown'); // returns YourDefaultEnum::Default +MyDefaultConstEnum::default(); //returns MyDefaultEnum::MyDefaultEnum +MyDefaultConstEnum::MyDefaultEnum->isDefault(); // returns true +MyDefaultConstEnum::MyDefaultEnum->isNotDefault(); // returns false +MyDefaultConstEnum::get('default'); // MyDefaultEnum::MyDefaultEnum +MyDefaultConstEnum::get('unknown'); // crashes +MyDefaultConstEnum::tryGet('default'); // returns MyDefaultEnum::MyDefaultEnum +MyDefaultConstEnum::tryGet('unknown'); // returns MyDefaultEnum::MyDefaultEnum + MyDefaultEnum::default(); //returns MyDefaultEnum::MyDefaultEnum MyDefaultEnum::MyDefaultEnum->isDefault(); // returns true MyDefaultEnum::MyDefaultEnum->isNotDefault(); // returns false diff --git a/docs/from.md b/docs/from.md index 481facf..d8faf56 100644 --- a/docs/from.md +++ b/docs/from.md @@ -39,11 +39,8 @@ YourEnum::tryFrom(YourOtherEnum::MyOtherEnum); // will return null; Note: Under the hood it uses the Getters functionality, so you can use lower- and uppercase names, and you can also use [Mappers](mappers.md) that you have -defined using the `mappers` method and the [Defaults](mappers.md). - -By default, from and tryFrom do not use [Mappers](mappers.md). But when -an UnitEnum object is passed, it will try to map the value first. If -no match is found, it will use the `name`. +defined as described in [Defaults](mappers.md). Warning: Be aware that this feature will not work if you have a backed enum. Even when you use this trait, currently, the original methods take precedence. +For those situations use [Getters](getters.md) diff --git a/docs/getters.md b/docs/getters.md index 8dfb750..2de19e9 100644 --- a/docs/getters.md +++ b/docs/getters.md @@ -15,6 +15,8 @@ enum YourEnum: string { case ENUM = 'your_enum'; case ENUM2 = 'your_other_enum'; + + const ConstantEnum = self::ENUM; } ``` @@ -23,6 +25,7 @@ enum YourEnum: string { ```php /** get */ YourEnum::get('ENUM'); // returns YourEnum::ENUM +YourEnum::get('ConstantEnum'); // returns YourEnum::ENUM YourEnum::get('0'); // returns YourEnum::ENUM YourEnum::get(0); // returns YourEnum::ENUM YourEnum::get('ENUM2'); // returns YourEnum::ENUM2 @@ -34,6 +37,7 @@ YourEnum::get('ENUM3'); // throws exception /** tryGet */ YourEnum::tryGet('ENUM'); // returns YourEnum::ENUM +YourEnum::tryGet('ConstantEnum'); // returns YourEnum::ENUM YourEnum::tryGet('0'); // returns YourEnum::ENUM YourEnum::tryGet(1); // returns YourEnum::ENUM YourEnum::tryGet('ENUM2'); // returns YourEnum::ENUM2 diff --git a/docs/mappers.md b/docs/mappers.md index 187d449..467336c 100644 --- a/docs/mappers.md +++ b/docs/mappers.md @@ -150,6 +150,61 @@ enum YourEnum { } ``` +## Mapping with arrays + +You can specify a map just by passing an array. This way you don't +have to create a `Mapper` class. + +```php +use Henzeb\Enumhancer\Concerns\Mappers; + +enum Suit { +use Mappers, Getters; + + case Spades; + case Diamonds; + + public static function mapper() : array + { + return [ + 'schoppen' => self::Spades, + 'ruiten' => self::Diamonds + ]; + } +} + +Suit::get('schoppen'); // returns self::Spades +Suit::get('ruiten'); // returns self::Diamonds +``` + +## Mapping with a constant + +You can also specify a map inside a constant. This way you don't +need to add a static `mapper` method and this allows you to use +[Configurable](configure.md#configuremapper) for mappers. + +```php +use Henzeb\Enumhancer\Concerns\Mappers; + +enum Suit { + use Mappers, Getters; + + case Spades; + case Diamonds; + + private const MAP_SPADES = [ + 'schoppen' => self::Spades + ]; + + private const map_diamonds = [ + 'ruiten' => self::Diamonds + ]; +} + +Suit::get('schoppen'); // returns self::Spades +Suit::get('ruiten'); // returns self::Diamonds +``` + ## Flip In some cases you might want to map two enums to each other. This is already @@ -177,7 +232,7 @@ enum Animal case Dog; case Cat; - protected static function mapper(): ?Mapper + protected static function mapper(): Mapper { return new AnimalMapper(); } @@ -190,7 +245,7 @@ enum LatinAnimalName case Canine; case Feline; - public static function mapper(): ?Mapper + public static function mapper(): Mapper { return AnimalMapper::flip(); } diff --git a/docs/value.md b/docs/value.md index 1479f63..a94e79e 100644 --- a/docs/value.md +++ b/docs/value.md @@ -32,3 +32,27 @@ Note: When used with a string or int backed enum, `value` will return it's actual value. Note: When used with an int backed enum, `key` will return the value. + +## Strict values + +By default, the value of a UnitEnum will be the lower cased value of the +enum case. With strict, you can modify this behavior so it returns uppercase. + +### Strict example + +```php +use Henzeb\Enumhancer\Concerns\Value; + +enum yourEnum { + use Value; + + const STRICT = true; + + case MY_ENUM; + case Other; + +} + +yourEnum::Other->value(); // returns Other +yourEnum::MY_ENUM->value(); // returns MY_ENUM +``` diff --git a/src/Concerns/ConfigureMapper.php b/src/Concerns/ConfigureMapper.php index 039bc6d..a0b879d 100644 --- a/src/Concerns/ConfigureMapper.php +++ b/src/Concerns/ConfigureMapper.php @@ -12,7 +12,7 @@ trait ConfigureMapper /** * @throws ReservedPropertyNameException|PropertyAlreadyStoredException */ - public static function setMapper(?Mapper $mapper): void + public static function setMapper(Mapper|array|null $mapper): void { EnumProperties::store( self::class, @@ -25,7 +25,7 @@ public static function setMapper(?Mapper $mapper): void /** * @throws ReservedPropertyNameException|PropertyAlreadyStoredException */ - public static function setMapperOnce(Mapper $mapper): void + public static function setMapperOnce(Mapper|array $mapper): void { EnumProperties::storeOnce( self::class, diff --git a/src/Concerns/Dropdown.php b/src/Concerns/Dropdown.php index cac0358..90087b8 100644 --- a/src/Concerns/Dropdown.php +++ b/src/Concerns/Dropdown.php @@ -4,6 +4,8 @@ use Henzeb\Enumhancer\Helpers\EnumCheck; use Henzeb\Enumhancer\Helpers\EnumSubsetMethods; +use ReflectionClass; +use ReflectionClassConstant; /** * @method static array cases() diff --git a/src/Concerns/Enhancers.php b/src/Concerns/Enhancers.php index ff220ef..9eaf859 100644 --- a/src/Concerns/Enhancers.php +++ b/src/Concerns/Enhancers.php @@ -6,6 +6,7 @@ trait Enhancers { use Comparison, Defaults, + Dropdown, From, Labels, Mappers, diff --git a/src/Concerns/From.php b/src/Concerns/From.php index 9a10933..7fc3686 100644 --- a/src/Concerns/From.php +++ b/src/Concerns/From.php @@ -9,11 +9,11 @@ trait From { public static function from(UnitEnum|string $key): self { - return EnumGetters::get(static::class, $key, $key instanceof UnitEnum, true); + return EnumGetters::get(static::class, $key, true, true); } public static function tryFrom(UnitEnum|string $key): ?self { - return EnumGetters::tryGet(static::class, $key, $key instanceof UnitEnum); + return EnumGetters::tryGet(static::class, $key, true); } } diff --git a/src/Concerns/Mappers.php b/src/Concerns/Mappers.php index edb1c0e..8bd399b 100644 --- a/src/Concerns/Mappers.php +++ b/src/Concerns/Mappers.php @@ -19,7 +19,7 @@ protected static function reporter(): ?Reporter return EnumReporter::get(); } - protected static function mapper(): ?Mapper + protected static function mapper(): Mapper|array|null { return EnumProperties::get( self::class, @@ -38,7 +38,7 @@ public static function make(string|int|UnitEnum|null $value, Mapper|string $mapp ); } - public static function get(string|int|UnitEnum|null $value, Mapper|string $mapper = null): self + public static function get(string|int|UnitEnum|null $value, Mapper|string|array $mapper = null): self { return EnumGetters::get( self::class, @@ -57,7 +57,7 @@ public static function tryMake(string|int|UnitEnum|null $value, Mapper|string $m ); } - public static function tryGet(string|int|UnitEnum|null $value, Mapper|string $mapper = null): ?self + public static function tryGet(string|int|UnitEnum|null $value, Mapper|string|array $mapper = null): ?self { return EnumGetters::tryGet( self::class, @@ -77,7 +77,7 @@ public static function makeArray(iterable $values, Mapper|string $mapper = null) ); } - public static function getArray(iterable $values, Mapper|string $mapper = null): array + public static function getArray(iterable $values, Mapper|string|array $mapper = null): array { return EnumGetters::getArray( self::class, @@ -96,7 +96,7 @@ public static function tryMakeArray(iterable $values, Mapper|string $mapper = nu ); } - public static function tryArray(iterable $values, Mapper|string $mapper = null): array + public static function tryArray(iterable $values, Mapper|string|array $mapper = null): array { return EnumGetters::tryArray( self::class, @@ -122,7 +122,7 @@ public static function makeOrReport( public static function getOrReport( int|string|UnitEnum|null $value, BackedEnum $context = null, - Mapper|string $mapper = null + Mapper|string|array $mapper = null ): ?self { return EnumReporter::getOrReport( self::class, @@ -150,7 +150,7 @@ public static function makeOrReportArray( public static function getOrReportArray( iterable $values, BackedEnum $context = null, - Mapper|string $mapper = null + Mapper|string|array $mapper = null ): array { return EnumReporter::getOrReportArray( self::class, @@ -160,7 +160,7 @@ public static function getOrReportArray( ); } - public static function extract(string $text, Mapper|string $mapper = null): array + public static function extract(string $text, Mapper|string|array $mapper = null): array { return EnumExtractor::extract(self::class, $text, $mapper, self::mapper()); } diff --git a/src/Helpers/EnumArrayMapper.php b/src/Helpers/EnumArrayMapper.php new file mode 100644 index 0000000..16bd271 --- /dev/null +++ b/src/Helpers/EnumArrayMapper.php @@ -0,0 +1,16 @@ +mappable; + } +} diff --git a/src/Helpers/EnumExtractor.php b/src/Helpers/EnumExtractor.php index ccb1e3c..80d94d3 100644 --- a/src/Helpers/EnumExtractor.php +++ b/src/Helpers/EnumExtractor.php @@ -10,7 +10,7 @@ abstract class EnumExtractor { use Mappers; - public static function extract(string $class, string $text, Mapper|string|null ...$mappers): array + public static function extract(string $class, string $text, Mapper|string|array|null ...$mappers): array { EnumCheck::check($class); diff --git a/src/Helpers/EnumGetters.php b/src/Helpers/EnumGetters.php index 90028cd..8381b4d 100644 --- a/src/Helpers/EnumGetters.php +++ b/src/Helpers/EnumGetters.php @@ -3,6 +3,8 @@ namespace Henzeb\Enumhancer\Helpers; use Henzeb\Enumhancer\Concerns\Getters; +use Henzeb\Enumhancer\Concerns\Mappers; +use ReflectionClass; use UnitEnum; use ValueError; use function Henzeb\Enumhancer\Functions\backingLowercase; @@ -19,7 +21,7 @@ public static function get( if ($useMapper && EnumImplements::mappers($class)) { /** - * @var $class Getters; + * @var $class Mappers; */ return $class::get($value); } @@ -129,7 +131,11 @@ private static function match(string $class, int|string|null $value): ?UnitEnum return $class::cases()[$value]; } - return self::findInCases($class, $value); + if (defined($class . '::' . $value) && constant($class . '::' . $value) instanceof $class) { + return constant($class . '::' . $value); + } + + return self::findInCases($class, $value) ?? self::findInConstants($class, $value); } private static function findInCases(string $class, int|string $value): ?UnitEnum @@ -164,4 +170,17 @@ protected static function useDefaultIf(string $class, int|string|null $value, bo } return null; } + + private static function findInConstants(UnitEnum|string $class, int|string $value): ?UnitEnum + { + $constants = (new ReflectionClass($class))->getConstants(); + + foreach ($constants as $name => $constant) { + if ($constant instanceof $class && strtolower($name) === strtolower($value)) { + return $constant; + } + } + + return null; + } } diff --git a/src/Helpers/EnumMapper.php b/src/Helpers/EnumMapper.php index a514f47..c14faba 100644 --- a/src/Helpers/EnumMapper.php +++ b/src/Helpers/EnumMapper.php @@ -3,13 +3,17 @@ namespace Henzeb\Enumhancer\Helpers; use Henzeb\Enumhancer\Contracts\Mapper; +use ReflectionClass; use RuntimeException; use UnitEnum; abstract class EnumMapper { - public static function map(string $enum, string|int|UnitEnum|null $value, Mapper|string|null ...$mappers): ?string - { + public static function map( + string $enum, + string|int|UnitEnum|null $value, + Mapper|array|string|null ...$mappers + ): ?string { EnumCheck::check($enum); if (null === $value) { @@ -18,6 +22,8 @@ public static function map(string $enum, string|int|UnitEnum|null $value, Mapper $mappers = self::sanitizeMapperArray(...$mappers); + self::constantMappers($enum, $mappers); + foreach ($mappers as $mapper) { $value = $mapper->map($value, $enum) ?? $value; } @@ -25,7 +31,7 @@ public static function map(string $enum, string|int|UnitEnum|null $value, Mapper return $value instanceof UnitEnum ? $value->name : $value; } - public static function mapArray(string $enum, iterable $values, Mapper|string|null ...$mappers): array + public static function mapArray(string $enum, iterable $values, Mapper|string|array|null ...$mappers): array { $mapped = []; @@ -36,16 +42,20 @@ public static function mapArray(string $enum, iterable $values, Mapper|string|nu return $mapped; } - public static function sanitizeMapperArray(Mapper|string|null ...$mappers): array + public static function sanitizeMapperArray(Mapper|string|array|null ...$mappers): array { return array_map( - function (Mapper|string $mapper) { + function (Mapper|array|string $mapper) { if (is_string($mapper)) { $mapper = new $mapper; } + if (is_array($mapper)) { + $mapper = self::wrapInMapper($mapper); + } + if (!$mapper instanceof Mapper) { throw new RuntimeException( sprintf( @@ -60,4 +70,20 @@ function (Mapper|string $mapper) { array_filter($mappers) ); } + + private static function constantMappers(string $enum, array &$mappers) + { + $constants = (new ReflectionClass($enum))->getConstants(); + + foreach ($constants as $name => $constant) { + if (str_starts_with(strtolower($name), 'map') && is_array($constant)) { + $mappers[] = self::wrapInMapper($constant); + } + } + } + + protected static function wrapInMapper(array $map): Mapper + { + return new EnumArrayMapper($map); + } } diff --git a/src/Helpers/EnumSubsetMethods.php b/src/Helpers/EnumSubsetMethods.php index 422b8ba..0c5d923 100644 --- a/src/Helpers/EnumSubsetMethods.php +++ b/src/Helpers/EnumSubsetMethods.php @@ -6,6 +6,7 @@ use Closure; use Henzeb\Enumhancer\Concerns\Labels; use Henzeb\Enumhancer\Contracts\EnumSubset; +use ReflectionClass; use UnitEnum; class EnumSubsetMethods implements EnumSubset @@ -78,7 +79,7 @@ function (mixed $enum) { ); } - public function dropdown(bool $keepEnumCase = false): array + public function dropdown(bool $keepEnumCase = null): array { return array_replace( [], diff --git a/src/Helpers/EnumValue.php b/src/Helpers/EnumValue.php index b81e294..8f913db 100644 --- a/src/Helpers/EnumValue.php +++ b/src/Helpers/EnumValue.php @@ -2,13 +2,30 @@ namespace Henzeb\Enumhancer\Helpers; +use ReflectionClass; use UnitEnum; -use BackedEnum; abstract class EnumValue { - public static function value(BackedEnum|UnitEnum $enum, bool $keepCase = false): string|int + public static function value(UnitEnum $enum, bool $keepCase = null): string|int { + if (is_null($keepCase)) { + $keepCase = self::isStrict($enum); + } + return $enum->value ?? ($keepCase ? $enum->name : strtolower($enum->name)); } + + private static function isStrict(UnitEnum $enum): bool + { + $constants = (new ReflectionClass($enum))->getConstants(); + + foreach ($constants as $name => $constant) { + if ('strict' === strtolower($name) && is_bool($constant)) { + return $constant; + } + } + + return false; + } } diff --git a/tests/Fixtures/EnhancedBackedEnum.php b/tests/Fixtures/EnhancedBackedEnum.php index 0f82dc7..c8c928c 100644 --- a/tests/Fixtures/EnhancedBackedEnum.php +++ b/tests/Fixtures/EnhancedBackedEnum.php @@ -2,9 +2,9 @@ namespace Henzeb\Enumhancer\Tests\Fixtures; -use Henzeb\Enumhancer\Contracts\Mapper; -use Henzeb\Enumhancer\Concerns\Enhancers; use Henzeb\Enumhancer\Concerns\Constructor; +use Henzeb\Enumhancer\Concerns\Enhancers; +use Henzeb\Enumhancer\Contracts\Mapper; use function Henzeb\Enumhancer\Functions\n; @@ -21,6 +21,16 @@ enum EnhancedBackedEnum: string case ENUM_3 = 'third_enum'; case WITH_CAPITALS = 'THIRD enum'; + const ConstantEnum = self::ENUM_3; + + private const MAP_CONSTANT = [ + 'expected' => self::WITH_CAPITALS + ]; + + public const MAP_CONSTANT_2 = [ + 'expected2' => self::ConstantEnum + ]; + protected function labels(): array { diff --git a/tests/Fixtures/UnitEnums/Defaults/DefaultsConstantEnum.php b/tests/Fixtures/UnitEnums/Defaults/DefaultsConstantEnum.php new file mode 100644 index 0000000..d9768a2 --- /dev/null +++ b/tests/Fixtures/UnitEnums/Defaults/DefaultsConstantEnum.php @@ -0,0 +1,18 @@ +assertNull( - FromWithMappersEnum::tryFrom('Translated') - ); - $this->assertEquals( FromWithMappersEnum::NotTranslated, FromWithMappersEnum::from(StringBackedGetEnum::Translated) ); - - $this->expectException(ValueError::class); - - FromWithMappersEnum::from('Translated'); - } } diff --git a/tests/Unit/Concerns/MappersTest.php b/tests/Unit/Concerns/MappersTest.php index cca9ba2..5530c31 100644 --- a/tests/Unit/Concerns/MappersTest.php +++ b/tests/Unit/Concerns/MappersTest.php @@ -15,7 +15,6 @@ public function getMapper() { return new class extends Mapper { - public function mappable(): array { return [ @@ -57,8 +56,8 @@ public function testMakeShouldMapWithStringMap() public function testMakeShouldThrowExceptionForNonMap() { - $this->expectException(\RuntimeException::class); - EnhancedBackedEnum::get('mappedEnum', self::class); + $this->expectException(\RuntimeException::class); + EnhancedBackedEnum::get('mappedEnum', self::class); } public function testMakeShouldNotMapWhenNull() @@ -313,4 +312,53 @@ public function testShouldAcceptEnumsAsValueArrays(): void $this->expectException(ValueError::class); EnhancedBackedEnum::getArray([EnhancedUnitEnum::Unique]); } + + public function testMapWithPassedArray(): void + { + $this->assertEquals( + EnhancedBackedEnum::ENUM, + EnhancedBackedEnum::get('passedByArray', ['passedByArray' => EnhancedBackedEnum::ENUM]) + ); + + $this->assertEquals( + [EnhancedBackedEnum::ENUM], + EnhancedBackedEnum::getArray(['passedByArray'], ['passedByArray' => EnhancedBackedEnum::ENUM]) + ); + } + + public function testMapWithConstants(): void + { + $this->assertEquals( + EnhancedBackedEnum::ENUM_3, + EnhancedBackedEnum::get('ConstantEnum') + ); + + $this->assertEquals( + [EnhancedBackedEnum::ENUM_3], + EnhancedBackedEnum::getArray(['ConstantEnum']) + ); + } + + public function testMapWithConstantsAsArray(): void + { + $this->assertEquals( + EnhancedBackedEnum::WITH_CAPITALS, + EnhancedBackedEnum::get('expected') + ); + + $this->assertEquals( + [EnhancedBackedEnum::WITH_CAPITALS], + EnhancedBackedEnum::getArray(['expected']) + ); + + $this->assertEquals( + EnhancedBackedEnum::ENUM_3, + EnhancedBackedEnum::get('expected2') + ); + + $this->assertEquals( + [EnhancedBackedEnum::ENUM_3], + EnhancedBackedEnum::getArray(['expected2']) + ); + } } diff --git a/tests/Unit/Concerns/ValueTest.php b/tests/Unit/Concerns/ValueTest.php index 0b1f585..96a6ae1 100644 --- a/tests/Unit/Concerns/ValueTest.php +++ b/tests/Unit/Concerns/ValueTest.php @@ -2,6 +2,7 @@ namespace Henzeb\Enumhancer\Tests\Unit\Concerns; +use Henzeb\Enumhancer\Tests\Fixtures\UnitEnums\Value\ValueStrictEnum; use PHPUnit\Framework\TestCase; use Henzeb\Enumhancer\Tests\Fixtures\EnhancedUnitEnum; use Henzeb\Enumhancer\Tests\Fixtures\EnhancedBackedEnum; @@ -17,6 +18,7 @@ public function providesEnumsForValue() 'backed-2' => [EnhancedBackedEnum::ANOTHER_ENUM, EnhancedBackedEnum::ANOTHER_ENUM->value], 'unit-1' => [EnhancedUnitEnum::ENUM, strtolower(EnhancedUnitEnum::ENUM->name)], 'unit-2' => [EnhancedUnitEnum::ANOTHER_ENUM, strtolower(EnhancedUnitEnum::ANOTHER_ENUM->name)], + 'unit-3' => [ValueStrictEnum::Strict, 'Strict'] ]; }