Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ parameters:
- tests
excludePaths:
- tests/Envs/*
checkGenericClassInNonGenericObjectType: false
ignoreErrors:
# -
# message: '#type has no value type specified in iterable type array#'
Expand Down
21 changes: 19 additions & 2 deletions src/EnvMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,22 @@ public function map(string $class, bool $requireValues = false, ?array $source =
* @return int|float|string|bool
* The passed value, but now with the correct type.
*/
private function typeNormalize(string $val, \ReflectionProperty $rProp): int|float|string|bool
private function typeNormalize(string $val, \ReflectionProperty $rProp): int|float|string|bool|\BackedEnum
{
$rType = $rProp->getType();
if ($rType instanceof \ReflectionNamedType) {
return match ($rType->getName()) {
$name = $rType->getName();

if (is_a($name, \BackedEnum::class, true)) {
$rEnum = new \ReflectionEnum($name);
$backingType = $rEnum->getBackingType();
assert($backingType instanceof \ReflectionNamedType);
$isIntBacked = $backingType->getName() === 'int';

return $name::from($isIntBacked ? (int) $val : $val);
}

return match ($name) {
'string' => $val,
'float' => is_numeric($val)
? (float) $val
Expand Down Expand Up @@ -118,6 +129,9 @@ protected function getDefaultValue(\ReflectionProperty $subject): mixed
}

/**
* @template T of object
* @param \ReflectionClass<T> $rClass
*
* @return array<string, \ReflectionParameter>
*/
protected function getConstructorArgs(\ReflectionClass $rClass): array
Expand All @@ -126,6 +140,9 @@ protected function getConstructorArgs(\ReflectionClass $rClass): array
}

/**
* @template T of object
* @param \ReflectionClass<T> $rClass
*
* @return array<string, \ReflectionParameter>
*/
protected function makeConstructorArgs(\ReflectionClass $rClass): array
Expand Down
8 changes: 8 additions & 0 deletions tests/EnvMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
use Crell\EnvMapper\Envs\EnvWithDefaults;
use Crell\EnvMapper\Envs\EnvWithMissingValue;
use Crell\EnvMapper\Envs\EnvWithTypeMismatch;
use Crell\EnvMapper\Envs\IntegerBackedEnum;
use Crell\EnvMapper\Envs\SampleEnvironment;
use Crell\EnvMapper\Envs\StringBackedEnum;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

Expand All @@ -22,6 +24,8 @@ class EnvMapperTest extends TestCase
'SHLVL' => '1',
'ZIP_CODE' => '01234',
'BOOL' => '1',
'STRING_BACKED_ENUM' => 'FOO',
'INTEGER_BACKED_ENUM' => '2',
];

#[Test]
Expand All @@ -37,6 +41,10 @@ public function mapping_different_types_with_defaults_parses_correctly(): void
self::assertNotNull($env->PATH);
self::assertNotNull($env->hostname);
self::assertNotNull($env->shlvl);
self::assertSame('01234', $env->zipCode);
self::assertSame(true, $env->bool);
self::assertEquals(StringBackedEnum::Foo, $env->stringBackedEnum);
self::assertEquals(IntegerBackedEnum::Bar, $env->integerBackedEnum);
self::assertEquals('default', $env->missing);
self::assertEquals(false, $env->missingFalse);
self::assertEquals('', $env->missingEmptyString);
Expand Down
11 changes: 11 additions & 0 deletions tests/Envs/IntegerBackedEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Crell\EnvMapper\Envs;

enum IntegerBackedEnum: int {
case Foo = 1;
case Bar = 2;
case Baz = 3;
}
3 changes: 3 additions & 0 deletions tests/Envs/SampleEnvironment.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public function __construct(
// This is a numeric string, but should stay a string.
public readonly string $zipCode,
public readonly bool $bool,
// These should be mapped using ::from()
public readonly StringBackedEnum $stringBackedEnum,
public readonly IntegerBackedEnum $integerBackedEnum,
// This is not defined in the environment, so the default value should be used.
public readonly string $missing = 'default',
// These are not defined in the environment, so the default falsy values should be used.
Expand Down
11 changes: 11 additions & 0 deletions tests/Envs/StringBackedEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Crell\EnvMapper\Envs;

enum StringBackedEnum: string {
case Foo = 'FOO';
case Bar = 'BAR';
case Baz = 'BAZ';
}