diff --git a/composer.json b/composer.json
index 924f924..6d37f2f 100644
--- a/composer.json
+++ b/composer.json
@@ -28,6 +28,6 @@
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "1.*",
- "vimeo/psalm": "^4.6.2"
+ "vimeo/psalm": "4.6.2"
}
}
diff --git a/phpunit.xml b/phpunit.xml
index bd714f1..13b4060 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -10,4 +10,9 @@
./tests
+
+
+ src
+
+
diff --git a/psalm.xml b/psalm.xml
index ff06b66..24ed533 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -1,10 +1,11 @@
@@ -16,8 +17,6 @@
-
-
diff --git a/src/Enum.php b/src/Enum.php
index 6967ab5..5f8b068 100644
--- a/src/Enum.php
+++ b/src/Enum.php
@@ -93,9 +93,10 @@ public function __wakeup()
}
/**
+ * @psalm-pure
* @param mixed $value
* @return static
- * @psalm-return static
+ * @psalm-return static
*/
public static function from($value): self
{
@@ -127,7 +128,6 @@ public function getKey()
/**
* @psalm-pure
- * @psalm-suppress InvalidCast
* @return string
*/
public function __toString()
@@ -187,7 +187,6 @@ public static function values()
* Returns all possible values as an array
*
* @psalm-pure
- * @psalm-suppress ImpureStaticProperty
*
* @psalm-return array
* @return array Constant name in key, constant value in value
@@ -212,8 +211,9 @@ public static function toArray()
* @param $value
* @psalm-param mixed $value
* @psalm-pure
- * @psalm-assert-if-true T $value
* @return bool
+ *
+ * deprecated use {@see Enum::isValidEnumValue()} instead
*/
public static function isValid($value)
{
@@ -224,8 +224,23 @@ public static function isValid($value)
* Asserts valid enum value
*
* @psalm-pure
- * @psalm-assert T $value
+ * @psalm-template CheckedValueType
+ * @psalm-param class-string> $enumType
* @param mixed $value
+ * @psalm-assert-if-true CheckedValueType $value
+ */
+ public static function isValidEnumValue(string $enumType, $value): bool
+ {
+ return $enumType::isValid($value);
+ }
+
+ /**
+ * Asserts valid enum value
+ *
+ * @psalm-pure
+ * @param mixed $value
+ *
+ * deprecated use {@see Enum::assertValidEnumValue()} instead
*/
public static function assertValidValue($value): void
{
@@ -236,7 +251,20 @@ public static function assertValidValue($value): void
* Asserts valid enum value
*
* @psalm-pure
- * @psalm-assert T $value
+ * @psalm-template ValueType
+ * @psalm-param class-string> $enumType
+ * @param mixed $value
+ * @psalm-assert ValueType $value
+ */
+ public static function assertValidEnumValue(string $enumType, $value): void
+ {
+ $enumType::assertValidValue($value);
+ }
+
+ /**
+ * Asserts valid enum value
+ *
+ * @psalm-pure
* @param mixed $value
* @return string
*/
diff --git a/static-analysis/EnumInstantiation.php b/static-analysis/EnumInstantiation.php
new file mode 100644
index 0000000..322cca8
--- /dev/null
+++ b/static-analysis/EnumInstantiation.php
@@ -0,0 +1,48 @@
+
+ */
+final class InstantiatedEnum extends Enum
+{
+ const A = 'A';
+ const C = 'C';
+}
+
+/**
+ * @psalm-pure
+ * @psalm-return InstantiatedEnum<'A'>
+ */
+function canCallConstructorWithConstantValue(): InstantiatedEnum
+{
+ return new InstantiatedEnum('A');
+}
+
+/**
+ * @psalm-pure
+ * @psalm-return InstantiatedEnum<'C'>
+ */
+function canCallConstructorWithConstantReference(): InstantiatedEnum
+{
+ return new InstantiatedEnum(InstantiatedEnum::C);
+}
+
+/** @psalm-pure */
+function canCallFromWithKnownValue(): InstantiatedEnum
+{
+ return InstantiatedEnum::from('C');
+}
+
+/** @psalm-pure */
+function canCallFromWithUnknownValue(): InstantiatedEnum
+{
+ return InstantiatedEnum::from(123123);
+}
diff --git a/static-analysis/EnumIsPure.php b/static-analysis/EnumIsPure.php
index 5875fd8..696694e 100644
--- a/static-analysis/EnumIsPure.php
+++ b/static-analysis/EnumIsPure.php
@@ -11,7 +11,7 @@
* @method static PureEnum C()
*
* @psalm-immutable
- * @psalm-template T of 'A'|'B'
+ * @psalm-template T of 'A'|'C'
* @template-extends Enum
*/
final class PureEnum extends Enum
diff --git a/static-analysis/EnumValidation.php b/static-analysis/EnumValidation.php
new file mode 100644
index 0000000..c6d7ba0
--- /dev/null
+++ b/static-analysis/EnumValidation.php
@@ -0,0 +1,83 @@
+
+ */
+final class ValidationEnum extends Enum
+{
+ const A = 'A';
+ const C = 'C';
+}
+
+/**
+ * @psalm-pure
+ * @param mixed $input
+ * @psalm-return 'A'|'C'
+ *
+ * @psalm-suppress MixedReturnStatement
+ * @psalm-suppress MixedInferredReturnType at the time of this writing, we did not yet find
+ * a proper approach to constraint input values through
+ * validation via static methods.
+ */
+function canValidateValue($input): string
+{
+ ValidationEnum::assertValidValue($input);
+
+ return $input;
+}
+
+/**
+ * @psalm-pure
+ * @param mixed $input
+ * @psalm-return 'A'|'C'
+ */
+function canAssertValidEnumValue($input): string
+{
+ ValidationEnum::assertValidEnumValue(ValidationEnum::class, $input);
+
+ return $input;
+}
+
+/**
+ * @psalm-pure
+ * @param mixed $input
+ * @psalm-return 'A'|'C'
+ *
+ * @psalm-suppress MixedReturnStatement
+ * @psalm-suppress MixedInferredReturnType at the time of this writing, we did not yet find
+ * a proper approach to constraint input values through
+ * validation via static methods.
+ */
+function canValidateValueThroughIsValid($input): string
+{
+ if (! ValidationEnum::isValid($input)) {
+ throw new \InvalidArgumentException('Value not valid');
+ }
+
+ return $input;
+}
+
+/**
+ * @psalm-pure
+ * @param mixed $input
+ * @psalm-return 'A'|'C'|1
+ *
+ * @psalm-suppress InvalidReturnType https://github.com/vimeo/psalm/issues/5372
+ * @psalm-suppress InvalidReturnStatement https://github.com/vimeo/psalm/issues/5372
+ */
+function canValidateValueThroughIsValidEnumValue($input)
+{
+ if (! ValidationEnum::isValidEnumValue(ValidationEnum::class, $input)) {
+ return 1;
+ }
+
+ return $input;
+}
diff --git a/tests/EnumTest.php b/tests/EnumTest.php
index 6fcc5b1..37559ea 100755
--- a/tests/EnumTest.php
+++ b/tests/EnumTest.php
@@ -6,6 +6,8 @@
namespace MyCLabs\Tests\Enum;
+use MyCLabs\Enum\Enum;
+
/**
* @author Matthieu Napoli
* @author Daniel Costa
@@ -59,6 +61,27 @@ public function testFailToCreateEnumWithInvalidValueThroughNamedConstructor($val
EnumFixture::from($value);
}
+ /**
+ * @dataProvider invalidValueProvider
+ * @param mixed $value
+ */
+ public function testRejectInvalidEnumValueWhenAssertingOnIt($value): void
+ {
+ $this->expectException(\UnexpectedValueException::class);
+ $this->expectExceptionMessage('is not part of the enum MyCLabs\Tests\Enum\EnumFixture');
+
+ Enum::assertValidEnumValue(EnumFixture::class, $value);
+ }
+
+ /**
+ * @dataProvider invalidValueProvider
+ * @param mixed $value
+ */
+ public function testReportsFalseOnNonValidEnumValueUponChecking($value): void
+ {
+ self::assertFalse(Enum::isValidEnumValue(EnumFixture::class, $value));
+ }
+
public function testFailToCreateEnumWithEnumItselfThroughNamedConstructor(): void
{
$this->expectException(\UnexpectedValueException::class);
@@ -67,6 +90,11 @@ public function testFailToCreateEnumWithEnumItselfThroughNamedConstructor(): voi
EnumFixture::from(EnumFixture::FOO());
}
+ public function testCreateEnumWithValidEnumValueThroughNamedConstructor(): void
+ {
+ self::assertEquals(EnumFixture::FOO(), EnumFixture::from('foo'));
+ }
+
/**
* Contains values not existing in EnumFixture
* @return array
@@ -179,7 +207,8 @@ public function testBadStaticAccess()
*/
public function testIsValid($value, $isValid)
{
- $this->assertSame($isValid, EnumFixture::isValid($value));
+ self::assertSame($isValid, EnumFixture::isValid($value));
+ self::assertSame($isValid, Enum::isValidEnumValue(EnumFixture::class, $value));
}
public function isValidProvider()
@@ -381,4 +410,19 @@ public function testAssertValidValue($value, $isValid): void
self::assertTrue(EnumFixture::isValid($value));
}
+
+ /**
+ * @dataProvider isValidProvider
+ */
+ public function testAssertValidValueWithTypedApi($value, $isValid): void
+ {
+ if (!$isValid) {
+ $this->expectException(\UnexpectedValueException::class);
+ $this->expectExceptionMessage("Value '$value' is not part of the enum " . EnumFixture::class);
+ }
+
+ Enum::assertValidEnumValue(EnumFixture::class, $value);
+
+ self::assertTrue(Enum::isValidEnumValue(EnumFixture::class, $value));
+ }
}