Skip to content

Commit

Permalink
feat(core) : add basic enumerable and SplReflectionNamedType
Browse files Browse the repository at this point in the history
  • Loading branch information
donaldinou committed Oct 8, 2024
1 parent 42e260b commit f51c13c
Show file tree
Hide file tree
Showing 37 changed files with 664 additions and 136 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `cases` method from [Basic enumerations]
- Add `from` method from [Backed enumerations]
- Add `tryFrom` method from [Backed enumerations]
- Add [SplEnumerable] in order to render [SplEnum] more generic and usable with [SplReflectionEnum]
- Add [SplUnitEnum] and abstract class [SplEnumUnit] in order to emulate [Basic enumerations] (PHP Pure enum) and [UnitEnum]
- Add [SplBackedEnum] and abstract class [SplEnumBacked] in order to emulate [Backed enumerations] and [BackedEnum]
- Add [SplIntEnum]
Expand All @@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
-- [SplReflectionEnum] in order to simulate [ReflectionEnum]
-- [SplReflectionEnumBackedCase] in order to simulate [ReflectionEnumBackedCase]
-- [SplReflectionEnumUnitCase] in order to simulate [ReflectionEnumUnitCase]
-- [SplReflectionNamedType] in order to simulate [ReflectionNamedType]
- Add [SplIntEnum] in order to simulate [Backed enumerations] typed as `int`
- Add [SplStringEnum] in order to simulate [Backed enumerations] typed as `string`
- Add `stubs` in order to use classes at root namespace
Expand Down Expand Up @@ -158,6 +160,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[`SplEnum`]: /assets/documentation/classes/SplEnum.md
[`SplBool`]: /assets/documentation/classes/SplBool.md
[`SplString`]: /assets/documentation/classes/SplString.md
[`SplEnumerable`]: /assets/documentation/classes/SplEnumerable.md
[`SplUnitEnum`]: /assets/documentation/classes/SplUnitEnum.md
[`SplBackedEnum`]: /assets/documentation/classes/SplBackedEnum.md
[`SplEnumUnit`]: /assets/documentation/classes/SplEnumUnit.md
Expand All @@ -170,6 +173,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[`SplReflectionEnumUnitCase`]: /assets/documentation/classes/Reflection/SplReflectionEnumUnitCase.md
[`SplReflectionEnumHelper`]: /assets/documentation/classes/Reflection/SplReflectionEnumHelper.md
[`SplReflectionEnumProxy`]: /assets/documentation/classes/Reflection/SplReflectionEnumProxy.md
[`SplReflectionNamedType`]: /assets/documentation/classes/Reflection/SplReflectionNamedType.md
[`Tools`]: /assets/documentation/classes/Util/Tools.md
[SplType::__construct]: /assets/documentation/classes/SplType.construct.md#SplType::__construct
[Tools::isSplEnumExists]: /assets/documentation/classes/Util/Tools.isSplEnumExist.md#Tools::isSplEnumExists
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ This library is released under the [MIT license].
[`SplEnum`]: /assets/documentation/classes/SplEnum.md
[`SplBool`]: /assets/documentation/classes/SplBool.md
[`SplString`]: /assets/documentation/classes/SplString.md
[`SplEnumerable`]: /assets/documentation/classes/SplEnumerable.md
[`SplUnitEnum`]: /assets/documentation/classes/SplUnitEnum.md
[`SplBackedEnum`]: /assets/documentation/classes/SplBackedEnum.md
[Lexique]: /assets/documentation/Lexique.md
Expand Down
22 changes: 14 additions & 8 deletions Reflection/SplReflectionEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@

namespace Ducks\Component\SplTypes\Reflection;

use Ducks\Component\SplTypes\SplUnitEnum;
use Ducks\Component\SplTypes\SplEnumerable;

/**
* The ReflectionEnum class reports information about an Enum.
*
* @template T of SplUnitEnum
* @extends \ReflectionClass<\Ducks\Component\SplTypes\SplUnitEnum>
* @template T of SplEnumerable
* @extends \ReflectionClass<\Ducks\Component\SplTypes\SplEnumerable>
*
* @psalm-api
*
* @link https://php.net/manual/en/class.reflectionenum.php
*/
Expand All @@ -28,7 +30,9 @@ class SplReflectionEnum extends \ReflectionClass
/**
* Array of proxy
*
* @var array<int, SplReflectionEnumProxy>
* @var SplReflectionEnumProxy[]
*
* @phpstan-var array<string, SplReflectionEnumProxy>
*/
private static array $instances = [];

Expand All @@ -53,16 +57,18 @@ private function getProxy(): SplReflectionEnumProxy
*
* @param object|string $objectOrClass
*
* @throws \ReflectionException if objectOrClass is not a SplUnitEnum
* @throws \ReflectionException if objectOrClass is not a SplEnumerable
*
* @phpstan-param T|class-string<T> $objectOrClass
*
* @phpstan-param SplUnitEnum|class-string<T> $objectOrClass
* @psalm-pure
*
* @link https://www.php.net/manual/en/reflectionenum.construct.php
*/
public function __construct($objectOrClass)
{
// Fast check
if (!\is_a($objectOrClass, SplUnitEnum::class, true)) {
if (!\is_a($objectOrClass, SplEnumerable::class, true)) {
// @phpstan-ignore ternary.elseUnreachable
$classname = \is_object($objectOrClass) ? \get_class($objectOrClass) : $objectOrClass;
throw new \ReflectionException("Class \"$classname\" is not an enum");
Expand Down Expand Up @@ -102,7 +108,7 @@ public function getCase(string $name): SplReflectionEnumUnitCase
/**
* Returns a list of all cases on an Enum
*
* @return array<int, SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
* @return (SplReflectionEnumUnitCase|SplReflectionEnumBackedCase)[]
*
* @phpstan-return list<SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
*
Expand Down
21 changes: 20 additions & 1 deletion Reflection/SplReflectionEnumBackedCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@

use Ducks\Component\SplTypes\SplUnitEnum;

/**
* The SplReflectionEnumBackedCase class reports info about an SplEnum backed case, which has no scalar equivalent.
*
* @psalm-api
* @psalm-immutable
* @psalm-suppress PropertyNotSetInConstructor
*/
class SplReflectionEnumBackedCase extends SplReflectionEnumUnitCase
{
/**
Expand All @@ -25,13 +32,21 @@ class SplReflectionEnumBackedCase extends SplReflectionEnumUnitCase
*
* @throws \ReflectionException if $class is not a \ReflectionEnumBackedCase
*
* @phpcs:ignore Generic.Files.LineLength.TooLong
* @phpstan-param \Ducks\Component\SplTypes\SplEnumerable|class-string<\Ducks\Component\SplTypes\SplEnumerable> $class An enum instance or a name.
* @phpstan-param string $constant An enum constant name.
*
* @psalm-suppress UninitializedProperty
* @psalm-suppress ImpureMethodCall
*
* @link https://www.php.net/manual/en/reflectionenumbackedcase.construct.php
*/
public function __construct($class, string $constant)
{
parent::__construct($class, $constant);

if (!$this->getEnum()->isBacked()) {
/** @psalm-suppress PossiblyNullOperand */
throw new \ReflectionException(
'Enum case ' . $this->class . '::' . $this->name . ' is not a backed case'
);
Expand All @@ -41,7 +56,7 @@ public function __construct($class, string $constant)
/**
* Gets the scalar value backing this Enum case
*
* @return int|string The scalar equivalent of this enum case.
* @return mixed The scalar equivalent of this enum case.
*
* @link https://www.php.net/manual/en/reflectionenumbackedcase.getbackingvalue.php
*/
Expand All @@ -53,6 +68,10 @@ public function getBackingValue()

/**
* {@inheritdoc}
*
* @return SplUnitEnum The enum case object described by this reflection object.
*
* @psalm-suppress ImpureMethodCall
*/
public function getValue(): SplUnitEnum
{
Expand Down
182 changes: 168 additions & 14 deletions Reflection/SplReflectionEnumHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,119 @@
*/
final class SplReflectionEnumHelper
{
private static ?\ReflectionNamedType $rnts = null;
private static ?\ReflectionNamedType $rnti = null;
/**
* Array of \ReflectionNamedType for types
*
* @var \ReflectionNamedType[]
*
* @phpstan-var array<string,\ReflectionNamedType>
*/
private static array $rnt = [];

/**
* @psalm-suppress UnusedConstructor
*/
private function __construct()
{
}

/**
* Get \ReflectionNamedType from internal ReflectionFunction closure.
*
* @param \ReflectionFunction $func
*
* @return \ReflectionNamedType
*/
private static function getReflectionNamedType(\ReflectionFunction $func): \ReflectionNamedType
{
/** @var \ReflectionNamedType $type */
$type = ($func->getParameters()[0])->getType();

return $type;
}

/**
* Get SplReflectionNamedType from a type.
*
* @param string $type
* @return \ReflectionNamedType
*
* @phpstan-param non-empty-string $type
* @phpstan-return \ReflectionNamedType
*/
private static function getTypedReflectionNamedType(string $type): \ReflectionNamedType
{
if (!isset(static::$rnt[$type])) {
static::$rnt[$type] = new SplReflectionNamedType($type);
}

return static::$rnt[$type];
}

/**
* Return ReflectionNamedType from a $type (Internal use).
*
* @param string $type
* @return \ReflectionNamedType
*/
public static function getReflectionNamedTypeFromType(string $type): \ReflectionNamedType
{
switch ($type) {
case 'string':
$result = self::getStringReflectionNamedType();
break;

case 'integer':
case 'int':
$result = self::getIntReflectionNamedType();
break;

case 'double':
case 'float':
$result = self::getFloatReflectionNamedType();
break;

case 'boolean':
case 'bool':
$result = self::getBoolReflectionNamedType();
break;

case 'array':
$result = self::getArrayReflectionNamedType();
break;

case 'object':
$result = self::getObjectReflectionNamedType();
break;

case 'mixed':
case 'unknown type':
$result = self::getTypedReflectionNamedType('mixed');
break;

default:
/** @phpstan-var non-empty-string $type */
$result = self::getTypedReflectionNamedType($type);
break;
}

return $result;
}

/**
* Only way to generate a string ReflectionNamedType (Internal use).
*
* @return \ReflectionNamedType
*/
public static function getStringReflectionNamedType(): \ReflectionNamedType
{
if (null === static::$rnts) {
$func = new \ReflectionFunction(static fn (string $param): string => $param);
// @phpstan-ignore-next-line
static::$rnts = ($func->getParameters()[0])->getType();
if (!isset(static::$rnt['string'])) {
static::$rnt['string'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn(string $param): string => $param)
);
}

// @phpstan-ignore-next-line
return static::$rnts;
return static::$rnt['string'];
}

/**
Expand All @@ -51,13 +142,76 @@ public static function getStringReflectionNamedType(): \ReflectionNamedType
*/
public static function getIntReflectionNamedType(): \ReflectionNamedType
{
if (null === static::$rnti) {
$func = new \ReflectionFunction(static fn (int $param): int => $param);
// @phpstan-ignore-next-line
static::$rnti = ($func->getParameters()[0])->getType();
if (!isset(static::$rnt['int'])) {
static::$rnt['int'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn (int $param): int => $param)
);
}

return static::$rnt['int'];
}

/**
* Only way to generate a float ReflectionNamedType (Internal use).
*
* @return \ReflectionNamedType
*/
public static function getFloatReflectionNamedType(): \ReflectionNamedType
{
if (!isset(static::$rnt['float'])) {
static::$rnt['float'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn(float $param): float => $param)
);
}

return static::$rnt['float'];
}

/**
* Only way to generate a bool ReflectionNamedType (Internal use).
*
* @return \ReflectionNamedType
*/
public static function getBoolReflectionNamedType(): \ReflectionNamedType
{
if (!isset(static::$rnt['bool'])) {
static::$rnt['bool'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn(bool $param): bool => $param)
);
}

return static::$rnt['bool'];
}

/**
* Only way to generate an array ReflectionNamedType (Internal use).
*
* @return \ReflectionNamedType
*/
public static function getArrayReflectionNamedType(): \ReflectionNamedType
{
if (!isset(static::$rnt['array'])) {
static::$rnt['array'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn(array $param): array => $param)
);
}

return static::$rnt['array'];
}

/**
* Only way to generate an object ReflectionNamedType (Internal use).
*
* @return \ReflectionNamedType
*/
public static function getObjectReflectionNamedType(): \ReflectionNamedType
{
if (!isset(static::$rnt['object'])) {
static::$rnt['object'] = static::getReflectionNamedType(
new \ReflectionFunction(static fn(object $param): object => $param)
);
}

// @phpstan-ignore-next-line
return static::$rnti;
return static::$rnt['object'];
}
}
Loading

0 comments on commit f51c13c

Please sign in to comment.