Skip to content

Commit f51c13c

Browse files
committedOct 8, 2024·
feat(core) : add basic enumerable and SplReflectionNamedType
1 parent 42e260b commit f51c13c

37 files changed

+664
-136
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Add `cases` method from [Basic enumerations]
1818
- Add `from` method from [Backed enumerations]
1919
- Add `tryFrom` method from [Backed enumerations]
20+
- Add [SplEnumerable] in order to render [SplEnum] more generic and usable with [SplReflectionEnum]
2021
- Add [SplUnitEnum] and abstract class [SplEnumUnit] in order to emulate [Basic enumerations] (PHP Pure enum) and [UnitEnum]
2122
- Add [SplBackedEnum] and abstract class [SplEnumBacked] in order to emulate [Backed enumerations] and [BackedEnum]
2223
- Add [SplIntEnum]
@@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2526
-- [SplReflectionEnum] in order to simulate [ReflectionEnum]
2627
-- [SplReflectionEnumBackedCase] in order to simulate [ReflectionEnumBackedCase]
2728
-- [SplReflectionEnumUnitCase] in order to simulate [ReflectionEnumUnitCase]
29+
-- [SplReflectionNamedType] in order to simulate [ReflectionNamedType]
2830
- Add [SplIntEnum] in order to simulate [Backed enumerations] typed as `int`
2931
- Add [SplStringEnum] in order to simulate [Backed enumerations] typed as `string`
3032
- Add `stubs` in order to use classes at root namespace
@@ -158,6 +160,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
158160
[`SplEnum`]: /assets/documentation/classes/SplEnum.md
159161
[`SplBool`]: /assets/documentation/classes/SplBool.md
160162
[`SplString`]: /assets/documentation/classes/SplString.md
163+
[`SplEnumerable`]: /assets/documentation/classes/SplEnumerable.md
161164
[`SplUnitEnum`]: /assets/documentation/classes/SplUnitEnum.md
162165
[`SplBackedEnum`]: /assets/documentation/classes/SplBackedEnum.md
163166
[`SplEnumUnit`]: /assets/documentation/classes/SplEnumUnit.md
@@ -170,6 +173,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
170173
[`SplReflectionEnumUnitCase`]: /assets/documentation/classes/Reflection/SplReflectionEnumUnitCase.md
171174
[`SplReflectionEnumHelper`]: /assets/documentation/classes/Reflection/SplReflectionEnumHelper.md
172175
[`SplReflectionEnumProxy`]: /assets/documentation/classes/Reflection/SplReflectionEnumProxy.md
176+
[`SplReflectionNamedType`]: /assets/documentation/classes/Reflection/SplReflectionNamedType.md
173177
[`Tools`]: /assets/documentation/classes/Util/Tools.md
174178
[SplType::__construct]: /assets/documentation/classes/SplType.construct.md#SplType::__construct
175179
[Tools::isSplEnumExists]: /assets/documentation/classes/Util/Tools.isSplEnumExist.md#Tools::isSplEnumExists

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ This library is released under the [MIT license].
7171
[`SplEnum`]: /assets/documentation/classes/SplEnum.md
7272
[`SplBool`]: /assets/documentation/classes/SplBool.md
7373
[`SplString`]: /assets/documentation/classes/SplString.md
74+
[`SplEnumerable`]: /assets/documentation/classes/SplEnumerable.md
7475
[`SplUnitEnum`]: /assets/documentation/classes/SplUnitEnum.md
7576
[`SplBackedEnum`]: /assets/documentation/classes/SplBackedEnum.md
7677
[Lexique]: /assets/documentation/Lexique.md

‎Reflection/SplReflectionEnum.php

+14-8
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
namespace Ducks\Component\SplTypes\Reflection;
1515

16-
use Ducks\Component\SplTypes\SplUnitEnum;
16+
use Ducks\Component\SplTypes\SplEnumerable;
1717

1818
/**
1919
* The ReflectionEnum class reports information about an Enum.
2020
*
21-
* @template T of SplUnitEnum
22-
* @extends \ReflectionClass<\Ducks\Component\SplTypes\SplUnitEnum>
21+
* @template T of SplEnumerable
22+
* @extends \ReflectionClass<\Ducks\Component\SplTypes\SplEnumerable>
23+
*
24+
* @psalm-api
2325
*
2426
* @link https://php.net/manual/en/class.reflectionenum.php
2527
*/
@@ -28,7 +30,9 @@ class SplReflectionEnum extends \ReflectionClass
2830
/**
2931
* Array of proxy
3032
*
31-
* @var array<int, SplReflectionEnumProxy>
33+
* @var SplReflectionEnumProxy[]
34+
*
35+
* @phpstan-var array<string, SplReflectionEnumProxy>
3236
*/
3337
private static array $instances = [];
3438

@@ -53,16 +57,18 @@ private function getProxy(): SplReflectionEnumProxy
5357
*
5458
* @param object|string $objectOrClass
5559
*
56-
* @throws \ReflectionException if objectOrClass is not a SplUnitEnum
60+
* @throws \ReflectionException if objectOrClass is not a SplEnumerable
61+
*
62+
* @phpstan-param T|class-string<T> $objectOrClass
5763
*
58-
* @phpstan-param SplUnitEnum|class-string<T> $objectOrClass
64+
* @psalm-pure
5965
*
6066
* @link https://www.php.net/manual/en/reflectionenum.construct.php
6167
*/
6268
public function __construct($objectOrClass)
6369
{
6470
// Fast check
65-
if (!\is_a($objectOrClass, SplUnitEnum::class, true)) {
71+
if (!\is_a($objectOrClass, SplEnumerable::class, true)) {
6672
// @phpstan-ignore ternary.elseUnreachable
6773
$classname = \is_object($objectOrClass) ? \get_class($objectOrClass) : $objectOrClass;
6874
throw new \ReflectionException("Class \"$classname\" is not an enum");
@@ -102,7 +108,7 @@ public function getCase(string $name): SplReflectionEnumUnitCase
102108
/**
103109
* Returns a list of all cases on an Enum
104110
*
105-
* @return array<int, SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
111+
* @return (SplReflectionEnumUnitCase|SplReflectionEnumBackedCase)[]
106112
*
107113
* @phpstan-return list<SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
108114
*

‎Reflection/SplReflectionEnumBackedCase.php

+20-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515

1616
use Ducks\Component\SplTypes\SplUnitEnum;
1717

18+
/**
19+
* The SplReflectionEnumBackedCase class reports info about an SplEnum backed case, which has no scalar equivalent.
20+
*
21+
* @psalm-api
22+
* @psalm-immutable
23+
* @psalm-suppress PropertyNotSetInConstructor
24+
*/
1825
class SplReflectionEnumBackedCase extends SplReflectionEnumUnitCase
1926
{
2027
/**
@@ -25,13 +32,21 @@ class SplReflectionEnumBackedCase extends SplReflectionEnumUnitCase
2532
*
2633
* @throws \ReflectionException if $class is not a \ReflectionEnumBackedCase
2734
*
35+
* @phpcs:ignore Generic.Files.LineLength.TooLong
36+
* @phpstan-param \Ducks\Component\SplTypes\SplEnumerable|class-string<\Ducks\Component\SplTypes\SplEnumerable> $class An enum instance or a name.
37+
* @phpstan-param string $constant An enum constant name.
38+
*
39+
* @psalm-suppress UninitializedProperty
40+
* @psalm-suppress ImpureMethodCall
41+
*
2842
* @link https://www.php.net/manual/en/reflectionenumbackedcase.construct.php
2943
*/
3044
public function __construct($class, string $constant)
3145
{
3246
parent::__construct($class, $constant);
3347

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

5469
/**
5570
* {@inheritdoc}
71+
*
72+
* @return SplUnitEnum The enum case object described by this reflection object.
73+
*
74+
* @psalm-suppress ImpureMethodCall
5675
*/
5776
public function getValue(): SplUnitEnum
5877
{

‎Reflection/SplReflectionEnumHelper.php

+168-14
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,119 @@
2020
*/
2121
final class SplReflectionEnumHelper
2222
{
23-
private static ?\ReflectionNamedType $rnts = null;
24-
private static ?\ReflectionNamedType $rnti = null;
23+
/**
24+
* Array of \ReflectionNamedType for types
25+
*
26+
* @var \ReflectionNamedType[]
27+
*
28+
* @phpstan-var array<string,\ReflectionNamedType>
29+
*/
30+
private static array $rnt = [];
2531

32+
/**
33+
* @psalm-suppress UnusedConstructor
34+
*/
2635
private function __construct()
2736
{
2837
}
2938

39+
/**
40+
* Get \ReflectionNamedType from internal ReflectionFunction closure.
41+
*
42+
* @param \ReflectionFunction $func
43+
*
44+
* @return \ReflectionNamedType
45+
*/
46+
private static function getReflectionNamedType(\ReflectionFunction $func): \ReflectionNamedType
47+
{
48+
/** @var \ReflectionNamedType $type */
49+
$type = ($func->getParameters()[0])->getType();
50+
51+
return $type;
52+
}
53+
54+
/**
55+
* Get SplReflectionNamedType from a type.
56+
*
57+
* @param string $type
58+
* @return \ReflectionNamedType
59+
*
60+
* @phpstan-param non-empty-string $type
61+
* @phpstan-return \ReflectionNamedType
62+
*/
63+
private static function getTypedReflectionNamedType(string $type): \ReflectionNamedType
64+
{
65+
if (!isset(static::$rnt[$type])) {
66+
static::$rnt[$type] = new SplReflectionNamedType($type);
67+
}
68+
69+
return static::$rnt[$type];
70+
}
71+
72+
/**
73+
* Return ReflectionNamedType from a $type (Internal use).
74+
*
75+
* @param string $type
76+
* @return \ReflectionNamedType
77+
*/
78+
public static function getReflectionNamedTypeFromType(string $type): \ReflectionNamedType
79+
{
80+
switch ($type) {
81+
case 'string':
82+
$result = self::getStringReflectionNamedType();
83+
break;
84+
85+
case 'integer':
86+
case 'int':
87+
$result = self::getIntReflectionNamedType();
88+
break;
89+
90+
case 'double':
91+
case 'float':
92+
$result = self::getFloatReflectionNamedType();
93+
break;
94+
95+
case 'boolean':
96+
case 'bool':
97+
$result = self::getBoolReflectionNamedType();
98+
break;
99+
100+
case 'array':
101+
$result = self::getArrayReflectionNamedType();
102+
break;
103+
104+
case 'object':
105+
$result = self::getObjectReflectionNamedType();
106+
break;
107+
108+
case 'mixed':
109+
case 'unknown type':
110+
$result = self::getTypedReflectionNamedType('mixed');
111+
break;
112+
113+
default:
114+
/** @phpstan-var non-empty-string $type */
115+
$result = self::getTypedReflectionNamedType($type);
116+
break;
117+
}
118+
119+
return $result;
120+
}
121+
30122
/**
31123
* Only way to generate a string ReflectionNamedType (Internal use).
32124
*
33125
* @return \ReflectionNamedType
34126
*/
35127
public static function getStringReflectionNamedType(): \ReflectionNamedType
36128
{
37-
if (null === static::$rnts) {
38-
$func = new \ReflectionFunction(static fn (string $param): string => $param);
39-
// @phpstan-ignore-next-line
40-
static::$rnts = ($func->getParameters()[0])->getType();
129+
if (!isset(static::$rnt['string'])) {
130+
static::$rnt['string'] = static::getReflectionNamedType(
131+
new \ReflectionFunction(static fn(string $param): string => $param)
132+
);
41133
}
42134

43-
// @phpstan-ignore-next-line
44-
return static::$rnts;
135+
return static::$rnt['string'];
45136
}
46137

47138
/**
@@ -51,13 +142,76 @@ public static function getStringReflectionNamedType(): \ReflectionNamedType
51142
*/
52143
public static function getIntReflectionNamedType(): \ReflectionNamedType
53144
{
54-
if (null === static::$rnti) {
55-
$func = new \ReflectionFunction(static fn (int $param): int => $param);
56-
// @phpstan-ignore-next-line
57-
static::$rnti = ($func->getParameters()[0])->getType();
145+
if (!isset(static::$rnt['int'])) {
146+
static::$rnt['int'] = static::getReflectionNamedType(
147+
new \ReflectionFunction(static fn (int $param): int => $param)
148+
);
149+
}
150+
151+
return static::$rnt['int'];
152+
}
153+
154+
/**
155+
* Only way to generate a float ReflectionNamedType (Internal use).
156+
*
157+
* @return \ReflectionNamedType
158+
*/
159+
public static function getFloatReflectionNamedType(): \ReflectionNamedType
160+
{
161+
if (!isset(static::$rnt['float'])) {
162+
static::$rnt['float'] = static::getReflectionNamedType(
163+
new \ReflectionFunction(static fn(float $param): float => $param)
164+
);
165+
}
166+
167+
return static::$rnt['float'];
168+
}
169+
170+
/**
171+
* Only way to generate a bool ReflectionNamedType (Internal use).
172+
*
173+
* @return \ReflectionNamedType
174+
*/
175+
public static function getBoolReflectionNamedType(): \ReflectionNamedType
176+
{
177+
if (!isset(static::$rnt['bool'])) {
178+
static::$rnt['bool'] = static::getReflectionNamedType(
179+
new \ReflectionFunction(static fn(bool $param): bool => $param)
180+
);
181+
}
182+
183+
return static::$rnt['bool'];
184+
}
185+
186+
/**
187+
* Only way to generate an array ReflectionNamedType (Internal use).
188+
*
189+
* @return \ReflectionNamedType
190+
*/
191+
public static function getArrayReflectionNamedType(): \ReflectionNamedType
192+
{
193+
if (!isset(static::$rnt['array'])) {
194+
static::$rnt['array'] = static::getReflectionNamedType(
195+
new \ReflectionFunction(static fn(array $param): array => $param)
196+
);
197+
}
198+
199+
return static::$rnt['array'];
200+
}
201+
202+
/**
203+
* Only way to generate an object ReflectionNamedType (Internal use).
204+
*
205+
* @return \ReflectionNamedType
206+
*/
207+
public static function getObjectReflectionNamedType(): \ReflectionNamedType
208+
{
209+
if (!isset(static::$rnt['object'])) {
210+
static::$rnt['object'] = static::getReflectionNamedType(
211+
new \ReflectionFunction(static fn(object $param): object => $param)
212+
);
58213
}
59214

60-
// @phpstan-ignore-next-line
61-
return static::$rnti;
215+
return static::$rnt['object'];
62216
}
63217
}

‎Reflection/SplReflectionEnumProxy.php

+65-43
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Ducks\Component\SplTypes\Reflection;
1515

1616
use Ducks\Component\SplTypes\SplBackedEnum;
17+
use Ducks\Component\SplTypes\SplEnumerable;
1718
use Ducks\Component\SplTypes\SplUnitEnum;
1819

1920
final class SplReflectionEnumProxy
@@ -44,17 +45,41 @@ final class SplReflectionEnumProxy
4445
/**
4546
* Array of constants class, indexed by name, as enum cases.
4647
*
47-
* @var array<string,\ReflectionClassConstant>
48+
* @var \ReflectionClassConstant[]
49+
*
50+
* @phpstan-var array<string,\ReflectionClassConstant>
4851
*/
4952
private array $constantCases = [];
5053

5154
/**
5255
* Array of Reflection enum cases, indexed by name.
5356
*
54-
* @var array<string, SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
57+
* @var (SplReflectionEnumUnitCase|SplReflectionEnumBackedCase)[]
58+
*
59+
* @phpstan-var array<string, SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
5560
*/
5661
private array $cases = [];
5762

63+
/**
64+
* The nameof the case beeing instanciate
65+
*
66+
* @var string|null
67+
*/
68+
private ?string $running = null;
69+
70+
/**
71+
* Name of the class constant.
72+
*
73+
* @var string
74+
*
75+
* @readonly
76+
*
77+
* @phpstan-var class-string
78+
*
79+
* @psalm-readonly
80+
*
81+
* @phan-read-only
82+
*/
5883
public string $name;
5984

6085
/**
@@ -65,14 +90,16 @@ final class SplReflectionEnumProxy
6590
public function __construct(\ReflectionClass $class)
6691
{
6792
$this->class = $class;
68-
$this->name = $class->name;
93+
$this->name = $class->getName();
6994
}
7095

7196
/**
7297
* Gets constants.
7398
*
74-
* @return array<string, mixed> An array of constants,
99+
* @return mixed[] An array of constants,
75100
* where the keys hold the name and the values the value of the constants.
101+
*
102+
* @phpstan-return array<string, mixed>
76103
*/
77104
public function getConstants(): array
78105
{
@@ -82,7 +109,7 @@ public function getConstants(): array
82109
/**
83110
* Gets a ReflectionClassConstant for a class's property
84111
*
85-
* @param string $name ? The class constant name.
112+
* @param string $name The class constant name.
86113
*
87114
* @return \ReflectionClassConstant|null
88115
*/
@@ -115,6 +142,8 @@ private function initConstantCases(): void
115142
* @param \ReflectionClassConstant ...$constants
116143
*
117144
* @return void
145+
*
146+
* @no-named-arguments
118147
*/
119148
public function addConstantCase(\ReflectionClassConstant ...$constants): void
120149
{
@@ -124,7 +153,7 @@ public function addConstantCase(\ReflectionClassConstant ...$constants): void
124153
!isset($this->constantCases[$name])
125154
&& $constant->isPublic()
126155
// Check consistency because of polyfilling or other bad overrides
127-
&& $constant->getDeclaringClass()->name === $this->name
156+
&& $constant->getDeclaringClass()->getName() === $this->name
128157
// Do not use isBacked method because of infinite loop possibility
129158
// Add if not BackedEnum or Backed but valid type
130159
&& (
@@ -143,7 +172,9 @@ public function addConstantCase(\ReflectionClassConstant ...$constants): void
143172
/**
144173
* Return an array of class constants, indexed by name, that could be use as an enum case.
145174
*
146-
* @return array<string,\ReflectionClassConstant>
175+
* @return \ReflectionClassConstant[]
176+
*
177+
* @phpstan-return array<string,\ReflectionClassConstant>
147178
*/
148179
public function getConstantCases(): array
149180
{
@@ -232,32 +263,30 @@ public function addCase(\ReflectionClassConstant ...$constants): void
232263
$name = $constant->getName();
233264
if (
234265
!isset($this->cases[$name])
266+
&& \is_a($this->name, SplEnumerable::class, true)
235267
) {
236268
// Check type
237269
$value = $constant->getValue();
238270

271+
// Mandatory in order to prevent infinite loop
272+
$this->running = $name;
273+
274+
if (!$this->isBacked() && null === $value) {
275+
$this->cases[$name] = new SplReflectionEnumUnitCase($this->name, $name);
276+
unset($this->running);
277+
continue;
278+
}
279+
280+
$backingType = $this->getBackingType();
281+
239282
if (
240-
(
241-
// Accept nullable value for UnitEnum
242-
null === $value && !($this->getBackingType() instanceof \ReflectionNamedType)
243-
) || (
244-
// Filter acceptable value for BackedEnum
245-
$this->getBackingType() instanceof \ReflectionNamedType
246-
&& isset($value)
247-
&& (
248-
\is_scalar($value) && \call_user_func('is_' . $this->getBackingType(), $value)
249-
|| \is_a($value, (string) $this->getBackingType())
250-
)
251-
)
283+
$this->isBacked()
284+
&& $backingType instanceof \ReflectionNamedType
285+
&& $backingType->getName() === \gettype($value)
252286
) {
253-
// Mandatory in order to prevent infinite loop
254-
$this->cases[$name] = true;
255-
$case = $this->isBacked()
256-
? new SplReflectionEnumBackedCase($this->name, $name)
257-
: new SplReflectionEnumUnitCase($this->name, $name);
258-
259-
// Now link correct class on pointer
260-
$this->cases[$name] = $case;
287+
$this->cases[$name] = new SplReflectionEnumBackedCase($this->name, $name);
288+
unset($this->running);
289+
continue;
261290
}
262291
}
263292
}
@@ -266,7 +295,9 @@ public function addCase(\ReflectionClassConstant ...$constants): void
266295
/**
267296
* Returns a list of all cases on an Enum
268297
*
269-
* @return array<string,SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
298+
* @return (SplReflectionEnumUnitCase|SplReflectionEnumBackedCase)[]
299+
*
300+
* @phpstan-return array<string,SplReflectionEnumUnitCase|SplReflectionEnumBackedCase>
270301
*
271302
* @link https://www.php.net/manual/en/reflectionenum.getcases.php
272303
*/
@@ -313,7 +344,8 @@ public function getCase(string $name): SplReflectionEnumUnitCase
313344
*/
314345
public function hasCase(string $name): bool
315346
{
316-
if (isset($this->cases[$name])) {
347+
// $this->cases could be empty
348+
if (isset($this->cases[$name]) || $this->running === $name) {
317349
return true;
318350
}
319351

@@ -339,7 +371,7 @@ public function isBacked(): bool
339371
$this->backed = false;
340372
} else {
341373
$constant = $this->getFirstCaseConstant();
342-
$this->backed = null !== $constant->getValue();
374+
$this->backed = $constant instanceof \ReflectionClassConstant && null !== $constant->getValue();
343375
}
344376
}
345377

@@ -364,19 +396,9 @@ public function getBackingType(): ?\ReflectionNamedType
364396
} else {
365397
$constant = $this->getFirstCaseConstant();
366398
if ($constant instanceof \ReflectionClassConstant) {
367-
switch (\gettype($constant->getValue())) {
368-
case 'string':
369-
$this->backingType = SplReflectionEnumHelper::getStringReflectionNamedType();
370-
break;
371-
372-
case 'integer':
373-
$this->backingType = SplReflectionEnumHelper::getIntReflectionNamedType();
374-
break;
375-
376-
default:
377-
$this->backingType = false;
378-
break;
379-
}
399+
$this->backingType = SplReflectionEnumHelper::getReflectionNamedTypeFromType(
400+
\gettype($constant->getValue())
401+
);
380402
}
381403
}
382404
} else {

‎Reflection/SplReflectionEnumUnitCase.php

+51-9
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@
1717

1818
/**
1919
* The SplReflectionEnumUnitCase class reports information about an SplEnum unit case, which has no scalar equivalent.
20+
*
21+
* @property-read class-string<\Ducks\Component\SplTypes\SplEnumerable> $class
22+
*
23+
* @psalm-api
24+
* @psalm-immutable
25+
* @psalm-suppress PropertyNotSetInConstructor
2026
*/
2127
class SplReflectionEnumUnitCase extends \ReflectionClassConstant
2228
{
2329
/**
2430
* Internal instances enum
2531
*
26-
* @var array<string,array<string,SplUnitEnum>>
32+
* @var array[]
33+
*
34+
* @phpstan-var array<string,array<string,SplUnitEnum>>
2735
*/
2836
private static array $instances = [];
2937

@@ -33,7 +41,14 @@ class SplReflectionEnumUnitCase extends \ReflectionClassConstant
3341
* @param object|string $class An enum instance or a name.
3442
* @param string $constant An enum constant name.
3543
*
36-
* @throws \ReflectionException if $class is not a SplReflectionEnumUnitCase
44+
* @throws \ReflectionException if $class is not a \Ducks\Component\SplTypes\SplEnumerable
45+
*
46+
* @phpcs:ignore Generic.Files.LineLength.TooLong
47+
* @phpstan-param \Ducks\Component\SplTypes\SplEnumerable|class-string<\Ducks\Component\SplTypes\SplEnumerable> $class An enum instance or a name.
48+
* @phpstan-param string $constant An enum constant name.
49+
*
50+
* @psalm-suppress UninitializedProperty
51+
* @psalm-suppress ImpureMethodCall
3752
*
3853
* @link https://www.php.net/manual/en/reflectionenumunitcase.construct.php
3954
*/
@@ -42,6 +57,7 @@ public function __construct($class, string $constant)
4257
parent::__construct($class, $constant);
4358

4459
if (!$this->getEnum()->hasCase($constant)) {
60+
/** @psalm-suppress PossiblyNullOperand */
4561
throw new \ReflectionException(
4662
'Enum case ' . $this->class . '::' . $this->name . ' is not a case'
4763
);
@@ -53,27 +69,55 @@ public function __construct($class, string $constant)
5369
*
5470
* @return SplReflectionEnum instance describing the Enum this case belongs to.
5571
*
56-
* @phpstan-return SplReflectionEnum<SplUnitEnum>
72+
* @throws \ReflectionException if objectOrClass is not a \Ducks\Component\SplTypes\SplEnumerable
5773
*
74+
* @phpstan-return SplReflectionEnum<\Ducks\Component\SplTypes\SplEnumerable>
75+
*
76+
* @psalm-suppress ArgumentTypeCoercion Needed because we need to throw Error on wrong type
77+
*
78+
* @see SplReflectionEnum::__construct()
5879
* @link https://www.php.net/manual/en/reflectionenumunitcase.getenum.php
5980
*/
6081
public function getEnum(): SplReflectionEnum
6182
{
83+
/** @var SplReflectionEnum<\Ducks\Component\SplTypes\SplEnumerable> */
6284
return new SplReflectionEnum($this->class);
6385
}
6486

6587
/**
6688
* Gets the enum case object described by this reflection object
6789
*
6890
* @return SplUnitEnum The enum case object described by this reflection object.
91+
*
92+
* @psalm-suppress MoreSpecificReturnType
93+
* @psalm-suppress LessSpecificReturnStatement
94+
* @psalm-suppress ImpureMethodCall
95+
* @psalm-suppress ImpureStaticProperty
6996
*/
7097
public function getValue(): SplUnitEnum
7198
{
7299
if (!isset(self::$instances[$this->class][$this->name])) {
73-
$class = $this->getDeclaringClass();
74-
$instance = $class->newInstanceWithoutConstructor();
100+
self::$instances[$this->class][$this->name] = $this->getEnumValue();
101+
}
102+
103+
return self::$instances[$this->class][$this->name];
104+
}
105+
106+
/**
107+
* Gets the enum from the class and constant name.
108+
*
109+
* @return SplUnitEnum
110+
*
111+
* @psalm-suppress ImpureMethodCall
112+
*/
113+
private function getEnumValue(): SplUnitEnum
114+
{
115+
$class = $this->getDeclaringClass();
116+
/** @var SplUnitEnum $instance */
117+
$instance = $class->newInstanceWithoutConstructor();
75118

76-
$object = $class->getConstructor();
119+
$object = $class->getConstructor();
120+
if ($object instanceof \ReflectionMethod) {
77121
$object->setAccessible(true);
78122
$object->invoke($instance);
79123

@@ -82,10 +126,8 @@ public function getValue(): SplUnitEnum
82126
$property->setAccessible(true);
83127
$property->setValue($instance, $this->name);
84128
}
85-
86-
self::$instances[$this->class][$this->name] = $instance;
87129
}
88130

89-
return self::$instances[$this->class][$this->name];
131+
return $instance;
90132
}
91133
}

‎Reflection/SplReflectionNamedType.php

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
namespace Ducks\Component\SplTypes\Reflection;
4+
5+
/**
6+
* ReflectionNamedType mock Class
7+
*
8+
* @psalm-immutable
9+
*/
10+
class SplReflectionNamedType extends \ReflectionNamedType
11+
{
12+
private const BUILT_IN_TYPES = [
13+
'int',
14+
'float',
15+
'string',
16+
'bool',
17+
'callable',
18+
'self',
19+
'parent',
20+
'array',
21+
'iterable',
22+
'object',
23+
'void',
24+
'mixed',
25+
'static',
26+
'null',
27+
'never',
28+
'false',
29+
'true',
30+
];
31+
32+
/**
33+
* The $name replacement
34+
*
35+
* @var string
36+
*
37+
* @phpstan-var non-empty-string
38+
*/
39+
private string $naming;
40+
41+
/**
42+
* The $allowsNull replacement
43+
*
44+
* @var boolean
45+
*/
46+
private bool $nullable;
47+
48+
/**
49+
* Build a fake \ReflectionNamedType
50+
*
51+
* @param string $name
52+
* @param boolean $nullable
53+
*
54+
* @phpstan-param non-empty-string $name
55+
* @phpstan-param boolean $nullable
56+
*/
57+
public function __construct(string $name, bool $nullable = false)
58+
{
59+
$this->naming = $name;
60+
61+
// Force nullable for native type name
62+
switch (\strtolower($name)) {
63+
case 'mixed':
64+
case 'null':
65+
$this->nullable = true;
66+
break;
67+
default:
68+
$this->nullable = $nullable;
69+
break;
70+
}
71+
}
72+
73+
/**
74+
* @inheritDoc
75+
*
76+
* @see https://www.php.net/manual/en/reflectionnamedtype.getname.php
77+
*/
78+
public function getName(): string
79+
{
80+
return $this->naming;
81+
}
82+
83+
/**
84+
* @inheritDoc
85+
*
86+
* @see https://php.net/manual/en/reflectiontype.isbuiltin.php
87+
*/
88+
public function isBuiltin(): bool
89+
{
90+
return \in_array(\strtolower($this->getName()), self::BUILT_IN_TYPES);
91+
}
92+
93+
/**
94+
* @inheritDoc
95+
*
96+
* @see https://www.php.net/manual/fr/reflectiontype.allowsnull.php
97+
*/
98+
public function allowsNull(): bool
99+
{
100+
return $this->nullable;
101+
}
102+
103+
/**
104+
* @inheritDoc
105+
*
106+
* @see https://www.php.net/manual/fr/reflectiontype.tostring.php
107+
*/
108+
public function __toString(): string
109+
{
110+
return $this->getName();
111+
}
112+
}

‎Resources/functions.php

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* @disregard P1009 Undefined type
1818
*
1919
* @phpstan-ignore-next-line
20+
*
21+
* @psalm-suppress UndefinedClass
2022
*/
2123
if (!\function_exists(spl_enum_exists::class)) {
2224
/**

‎SplBackedEnum.php

+23
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,27 @@
2222
*/
2323
interface SplBackedEnum extends SplUnitEnum
2424
{
25+
// public readonly string|int|mixed $value;
26+
27+
/**
28+
* Maps a scalar to an enum instance.
29+
*
30+
* @param int|string $value The scalar value to map to an enum case.
31+
*
32+
* @return static A case instance of this enumeration.
33+
*
34+
* @throws \ValueError if $value is not a valid backing value for enum
35+
*/
36+
public static function from($value): self;
37+
38+
/**
39+
* Maps a scalar to an enum instance or null.
40+
*
41+
* @param int|string|mixed $value e scalar value to map to an enum case.
42+
*
43+
* @return static|null A case instance of this enumeration, or null if not found.
44+
*
45+
* @psalm-suppress UnusedVariable
46+
*/
47+
public static function tryFrom($value): ?self;
2548
}

‎SplBackedEnumTrait.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*
2121
* @template T
2222
*
23+
* @property-read mixed $value
24+
*
2325
* @phpstan-require-implements SplBackedEnum
2426
*/
2527
trait SplBackedEnumTrait
@@ -54,12 +56,15 @@ final public static function from($value): self
5456
* @param int|string|mixed $value e scalar value to map to an enum case.
5557
*
5658
* @return static|null A case instance of this enumeration, or null if not found.
59+
*
60+
* @psalm-suppress UnusedVariable
5761
*/
5862
final public static function tryFrom($value): ?self
5963
{
6064
foreach (static::cases() as $case) {
6165
/**
6266
* @var SplEnumBacked $case
67+
*
6368
* @phpstan-var SplEnumBacked<T> $case
6469
*/
6570
if ($case->value === $value) {
@@ -68,15 +73,15 @@ final public static function tryFrom($value): ?self
6873
}
6974
}
7075

71-
/** @var static $result */
76+
/** @var static|null $result */
7277
return $result ?? null;
7378
}
7479

7580
/**
7681
* Return a new instance of enum.
7782
*
7883
* @param string $name
79-
* @param array<int,mixed> $arguments
84+
* @param mixed[] $arguments
8085
*
8186
* @return static self keywords not an equivalent
8287
*

‎SplBool.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
* @extends SplEnum<bool>
2020
* @implements SplBackedEnum<bool>
2121
*
22+
* @property-read mixed $value
23+
* @property-read string $name
24+
*
2225
* @psalm-api
2326
* @psalm-suppress MissingDependency
2427
* @psalm-suppress UndefinedClass
2528
*/
26-
class SplBool extends SplEnum implements SplBackedEnum
29+
class SplBool extends SplEnum implements SplBackedEnum, SplEnumSingletonable
2730
{
2831
/** @use SplBackedEnumTrait<bool> */
2932
use SplBackedEnumTrait;

‎SplEnum.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
* SplEnum gives the ability to emulate and create enumeration objects natively in PHP.
1818
*
1919
* @template T
20-
* @extends SplType<mixed>
20+
* @extends SplType<T>
2121
*
2222
* @psalm-api
2323
*
2424
* @psalm-suppress MissingDependency
2525
* @psalm-suppress UndefinedClass
2626
*/
27-
abstract class SplEnum extends SplType
27+
abstract class SplEnum extends SplType implements SplEnumerable
2828
{
2929
use SplEnumTrait;
3030
/** @use SplEnumAccessorsTrait<T> */
@@ -36,13 +36,17 @@ abstract class SplEnum extends SplType
3636
* @param mixed $initial_value
3737
* @param bool $strict
3838
*
39+
* @phpstan-param T|null $initial_value
40+
* @phpstan-param bool $strict
41+
*
3942
* @throws \UnexpectedValueException if incompatible type is given.
4043
*
4144
* @SuppressWarnings(PHPMD.CamelCaseParameterName)
4245
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
4346
*/
4447
public function __construct($initial_value = self::__default, bool $strict = true)
4548
{
49+
/** @var T $initial_value */
4650
$initial_value ??= static::__default;
4751

4852
if (!\in_array($initial_value, $this->getConstList(), $strict)) {
@@ -57,7 +61,7 @@ public function __construct($initial_value = self::__default, bool $strict = tru
5761
*
5862
* @param bool $include_default Whether to include __default property.
5963
*
60-
* @return array<mixed, mixed>
64+
* @return mixed[]
6165
*
6266
* @SuppressWarnings(PHPMD.CamelCaseParameterName)
6367
* @SuppressWarnings(PHPMD.CamelCaseVariableName)

‎SplEnumAccessorsTrait.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ trait SplEnumAccessorsTrait
2828
* @param string $name
2929
*
3030
* @return mixed
31-
*
32-
* @phpstan-return T|null
3331
*/
3432
final public function __get(string $name)
3533
{
@@ -53,11 +51,17 @@ final public function __get(string $name)
5351
/**
5452
* Writing data to inaccessible (protected or private) or non-existing properties.
5553
*
56-
* @throws \Error
54+
* @param string $name
55+
* @param mixed $value
5756
*
58-
* @psalm-suppress MissingParamType
57+
* @return void
58+
*
59+
* @throws \Error
5960
*
6061
* @phpstan-ignore-next-line
62+
*
63+
* @psalm-suppress MissingParamType
64+
* @psalm-suppress UnusedParam
6165
*/
6266
final public function __set(string $name, $value): void
6367
{

‎SplEnumBacked.php

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ abstract class SplEnumBacked extends SplEnumUnit implements SplBackedEnum
3434

3535
/**
3636
* {@inheritdoc}
37+
*
38+
* @psalm-suppress UnsupportedPropertyReferenceUsage
3739
*/
3840
protected function __construct()
3941
{

‎SplEnumBackedTrait.php

+37-14
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,20 @@
2626
trait SplEnumBackedTrait
2727
{
2828
use SplEnumUnitTrait {
29-
SplEnumUnitTrait::__serialize as __unitSerialize;
30-
SplEnumUnitTrait::__unserialize as __unitUnserialize;
31-
SplEnumUnitTrait::__set_state as __unitSetState;
29+
SplEnumUnitTrait::__serialize as private __unitSerialize;
30+
SplEnumUnitTrait::__unserialize as private __unitUnserialize;
31+
SplEnumUnitTrait::__set_state as private __unitSetState;
32+
SplEnumUnitTrait::__debugInfo as private __unitDebugInfo;
3233
}
3334

3435
/**
3536
* Serialize object.
3637
*
37-
* @return array<string,mixed>
38+
* @return mixed[]
39+
*
40+
* @phpstan-return array<string,mixed>
41+
*
42+
* @psalm-suppress LessSpecificImplementedReturnType
3843
*/
3944
public function __serialize(): array
4045
{
@@ -46,9 +51,14 @@ public function __serialize(): array
4651
/**
4752
* Unserialize object.
4853
*
49-
* @param array<string,mixed> $data
54+
* @param mixed[] $data
5055
*
5156
* @return void
57+
*
58+
* @phpstan-param array<string,mixed> $data
59+
* @phpstan-return void
60+
*
61+
* @psalm-suppress UnsupportedPropertyReferenceUsage
5262
*/
5363
public function __unserialize(array $data): void
5464
{
@@ -60,34 +70,47 @@ public function __unserialize(array $data): void
6070
/**
6171
* Instanciate an exported object.
6272
*
63-
* @param array<string,mixed> $properties
73+
* @param mixed[] $properties
6474
*
6575
* @return static
6676
*
77+
* @phpstan-param array<string,mixed> $properties
78+
* @phpstan-return static
79+
*
6780
* @psalm-suppress UnsafeInstantiation
6881
*/
6982
#[\ReturnTypeWillChange]
7083
public static function __set_state(array $properties): SplBackedEnum
7184
{
72-
/** @var static $object */
73-
$object = static::__unitSetState($properties);
74-
$object->value = $properties['value'];
85+
/** @phpstan-var T $value */
86+
$value = $properties['value'];
87+
88+
$object = self::__unitSetState($properties);
89+
$object->value = $value;
7590

91+
/** @var static $object */
7692
return $object;
7793
}
7894

7995
/**
8096
* Dumping object.
8197
*
82-
* @return array<string,string>
98+
* @return mixed[]
99+
*
100+
* @phpstan-return array<string,mixed>
101+
*
102+
* @psalm-suppress LessSpecificImplementedReturnType
83103
*
84104
* @codeCoverageIgnore
85105
*/
86106
public function __debugInfo(): array
87107
{
88-
return [
89-
'name' => $this->name,
90-
'value' => $this->value,
91-
];
108+
/** @phpstan-var T $value */
109+
$value = $this->value;
110+
111+
$result = $this->__unitDebugInfo();
112+
$result['value'] = $value;
113+
114+
return $result;
92115
}
93116
}

‎SplEnumSingletonTrait.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
1414
/**
1515
* Trait used for enum emulation
1616
*
17+
* @phpstan-require-extends SplEnum
18+
* @phpstan-require-implements SplEnumSingletonable
19+
*
1720
* @psalm-api
1821
*/
1922
trait SplEnumSingletonTrait
2023
{
2124
/**
22-
* Undocumented variable
25+
* internal instance of enum
2326
*
24-
* @var array<int,SplEnumSingletonable>
27+
* @var SplEnumSingletonable[]
2528
*
26-
* @phpstan-var list<SplEnumSingletonable>
29+
* @phpstan-var array<string,SplEnumSingletonable>
2730
*/
2831
private static array $instances = [];
2932

@@ -33,11 +36,17 @@ trait SplEnumSingletonTrait
3336
* @param string $name
3437
*
3538
* @return SplEnumSingletonable
39+
*
40+
* @throws \Error if $name is not a case
41+
*
42+
* @see SplEnum::__callStatic()
3643
*/
3744
public static function getInstance(string $name): SplEnumSingletonable
3845
{
3946
if (!isset(self::$instances[$name])) {
40-
self::$instances[$name] = static::$name();
47+
/** @var SplEnumSingletonable $instance */
48+
$instance = static::$name();
49+
self::$instances[$name] = $instance;
4150
}
4251

4352
return self::$instances[$name];

‎SplEnumTrait.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ trait SplEnumTrait
2424
* Return a new instance of enum
2525
*
2626
* @param string $name
27-
* @param array<int, mixed> $arguments
27+
* @param mixed[] $arguments
2828
*
2929
* @return static
3030
*
31+
* @throws \Error if $name is not a case
32+
*
3133
* @phpstan-param string $name
3234
* @phpstan-param list<mixed> $arguments
3335
* @phpstan-return static

‎SplEnumUnit.php

+1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ abstract class SplEnumUnit extends SplEnum implements
3333
*/
3434
protected function __construct()
3535
{
36+
// Cannot instantiate enum static::class
3637
}
3738
}

‎SplEnumUnitTrait.php

+23-10
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ trait SplEnumUnitTrait
2626
/**
2727
* Serialize object.
2828
*
29-
* @return array<string,mixed>
29+
* @return mixed[]
30+
*
31+
* @phpstan-return array<string,mixed>
3032
*/
3133
public function __serialize(): array
3234
{
@@ -39,47 +41,58 @@ public function __serialize(): array
3941
/**
4042
* Unserialize object.
4143
*
42-
* @param array<string,mixed> $data
44+
* @param mixed[] $data
4345
*
4446
* @return void
47+
*
48+
* @phpstan-param array<string,mixed> $data
49+
* @phpstan-return void
4550
*/
4651
public function __unserialize(array $data): void
4752
{
4853
parent::__unserialize($data);
4954

50-
$this->name = $data['name'];
55+
$this->name = (string) $data['name'];
5156
}
5257

5358
/**
5459
* Instanciate an exported object.
5560
*
56-
* @param array<string,mixed> $properties
61+
* @param mixed[] $properties
5762
*
5863
* @return static
5964
*
65+
* @phpstan-param array<string,mixed> $properties
66+
* @phpstan-return static
67+
*
6068
* @psalm-suppress UnsafeInstantiation
6169
*/
6270
public static function __set_state(array $properties): SplUnitEnum
6371
{
64-
/** @var static $object */
72+
/**
73+
* @var static $object
74+
*/
6575
// @phpstan-ignore-next-line
6676
$object = /** @scrutinizer ignore-call */ new static();
67-
$object->name = $properties['name'];
77+
$object->name = (string) $properties['name'];
6878

6979
return $object;
7080
}
7181

7282
/**
7383
* Dumping object.
7484
*
75-
* @return array<string,string>
85+
* @return string[]
86+
*
87+
* @phpstan-return array<string,mixed>
7688
*
7789
* @codeCoverageIgnore
7890
*/
7991
public function __debugInfo(): array
8092
{
81-
return [
82-
'name' => $this->name,
83-
];
93+
$result = parent::__debugInfo();
94+
$result['name'] = $this->name;
95+
96+
return $result;
8497
}
8598
}

‎SplEnumerable.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/**
4+
* Part of SplTypes package.
5+
*
6+
* (c) Adrien Loyant <donald_duck@team-df.org>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Ducks\Component\SplTypes;
15+
16+
/**
17+
* Interface used in order to set a class enumerable.
18+
*
19+
* @psalm-api
20+
*/
21+
interface SplEnumerable
22+
{
23+
// public readonly string $name;
24+
}

‎SplStringEnum.php

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ final protected function __construct()
3939
parent::__construct();
4040
}
4141

42+
/**
43+
* Method called when a script tries to call an object as a function.
44+
*
45+
* @return string
46+
*
47+
* @psalm-suppress MixedInferredReturnType
48+
*/
4249
final public function &__invoke(): string
4350
{
4451
return $this->__default;

‎SplType.php

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ abstract class SplType implements
2929

3030
/**
3131
* Default value.
32+
*
33+
* @var mixed
3234
*/
3335
// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
3436
protected const __default = null;
@@ -41,6 +43,8 @@ abstract class SplType implements
4143
*
4244
* @return void
4345
*
46+
* @phpstan-param T|null $initial_value Type and default value depends on the extension class.
47+
* @phpstan-param bool $strict Whether to set the object's sctrictness.
4448
* @phpstan-ignore-next-line
4549
*
4650
* @psalm-suppress PossiblyUnusedParam
@@ -51,6 +55,7 @@ abstract class SplType implements
5155
*/
5256
public function __construct($initial_value = self::__default, /** @scrutinizer ignore-unused */ bool $strict = true)
5357
{
58+
/** @var T $this->__default */
5459
$this->__default = $initial_value ?? static::__default;
5560
}
5661
}

‎SplTypeTrait.php

+11-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function jsonSerialize()
4848
/**
4949
* Serialize object.
5050
*
51-
* @return array<string,mixed>
51+
* @return mixed[]
5252
*
5353
* @phpstan-return array<string,T>
5454
*/
@@ -62,19 +62,25 @@ public function __serialize(): array
6262
/**
6363
* Unserialize object.
6464
*
65-
* @param array<string,mixed> $data
65+
* @param mixed[] $data
6666
*
6767
* @return void
68+
*
69+
* @phpstan-param array<string,mixed> $data
70+
* @phpstan-return void
6871
*/
6972
public function __unserialize(array $data): void
7073
{
74+
/** @phpstan-var T $data['__default'] */
7175
$this->__default = $data['__default'];
7276
}
7377

7478
/**
7579
* Method called when a script tries to call an object as a function.
7680
*
7781
* @return mixed
82+
*
83+
* @phpstan-return T
7884
*/
7985
#[\ReturnTypeWillChange]
8086
public function &__invoke()
@@ -95,14 +101,15 @@ final public function __toString(): string
95101
/**
96102
* Instanciate an exported object.
97103
*
98-
* @param array<string,mixed> $properties
104+
* @param mixed[] $properties
99105
*
100106
* @return static
101107
*
102108
* @phpstan-param array<string,T> $properties
103109
* @phpstan-return static
104110
*
105111
* @psalm-suppress UnsafeInstantiation
112+
* @psalm-suppress UndefinedDocblockClass
106113
*/
107114
public static function __set_state(array $properties): object
108115
{
@@ -113,7 +120,7 @@ public static function __set_state(array $properties): object
113120
/**
114121
* Dumping object.
115122
*
116-
* @return array<string,mixed>
123+
* @return mixed[]
117124
*
118125
* @phpstan-return array<string,T>
119126
*

‎SplUnitEnum.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* @psalm-api
2020
*/
21-
interface SplUnitEnum
21+
interface SplUnitEnum extends SplEnumerable
2222
{
2323
// public readonly string $name;
2424

‎SplUnitEnumTrait.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
use Ducks\Component\SplTypes\Reflection\SplReflectionEnumUnitCase;
1818

1919
/**
20+
* Simplify SplUnitEnum integration
21+
*
22+
* @property-read string $name
23+
*
2024
* @phpstan-require-implements SplUnitEnum
2125
*/
2226
trait SplUnitEnumTrait
@@ -31,7 +35,13 @@ trait SplUnitEnumTrait
3135
protected string $name;
3236

3337
/**
34-
* @inheritDoc
38+
* Generates a list of cases on an enum
39+
*
40+
* @return static[] An array of all defined cases of this enumeration, in order of declaration.
41+
*
42+
* @psalm-suppress MixedInferredReturnType
43+
*
44+
* @see SplUnitEnum::cases()
3545
*/
3646
public static function cases(): array
3747
{
@@ -40,7 +50,6 @@ public static function cases(): array
4050
if (null === $cases) {
4151
$enum = new SplReflectionEnum(static::class);
4252
foreach ($enum->getCases() as $case) {
43-
/** @var Reflection\SplReflectionEnumUnitCase $case */
4453
$cases[] = $case->getValue();
4554
}
4655
}
@@ -52,7 +61,7 @@ public static function cases(): array
5261
* Return a new instance of enum
5362
*
5463
* @param string $name
55-
* @param array<int, mixed> $arguments
64+
* @param mixed[] $arguments
5665
*
5766
* @return static self keywords not an equivalent
5867
*

‎Tests/benchmark/SplEnumBench.php

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function benchCreateSeptemberMonth(): void
5050
*/
5151
public function benchCreateUnstrictMonth(): void
5252
{
53+
// @phpstan-ignore argument.type
5354
new Month('1', false);
5455
}
5556
}

‎Tests/phpunit/SplEnumTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function test(): void
3131
$instance = new Month(Month::SEPTEMBER);
3232
$this->assertEquals(Month::SEPTEMBER, $instance());
3333

34+
// @phpstan-ignore argument.type
3435
$instance = new Month('1', false);
3536
$this->assertEquals(Month::JANUARY, $instance());
3637
}
@@ -45,6 +46,7 @@ public function test(): void
4546
public function testUnexpectedValueRxception(): void
4647
{
4748
$this->expectException(\UnexpectedValueException::class);
49+
// @phpstan-ignore argument.type
4850
new Month('1');
4951
}
5052

‎Util/Tools.php

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
final class Tools
1919
{
20+
/**
21+
* @psalm-suppress UnusedConstructor
22+
*/
2023
private function __construct()
2124
{
2225
}

‎assets/documentation/Lexique.md

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
## Interfaces
1313

14+
[`SplEnumerable`]
1415
[`SplUnitEnum`]
1516
[`SplBackedEnum`]
1617

@@ -28,6 +29,7 @@
2829
[`SplEnum`]: ./classes/SplEnum.md
2930
[`SplBool`]: ./classes/SplBool.md
3031
[`SplString`]: ./classes/SplString.md
32+
[`SplEnumerable`]: ./classes/SplEnumerable.md
3133
[`SplUnitEnum`]: ./classes/SplUnitEnum.md
3234
[`SplBackedEnum`]: ./classes/SplBackedEnum.md
3335
[`SplEnumUnit`]: ./classes/SplEnumUnit.md

‎assets/documentation/classes/Reflection/SplReflectionNamedType.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# [The SplEnumerable interface](#The-SplEnumerable-interface)
2+
3+
(PHP 7, PHP 8)
4+
5+
## [Introduction](#Introduction)
6+
7+
Interface used in order to set a class enumerable.
8+
9+
## [Interface synopsis](#Interface-synopsis)
10+
11+
```php
12+
interface SplEnumerable {
13+
}
14+
```
15+
16+
## [Table of Contents](#Table-of-Contents)

‎assets/documentation/classes/SplUnitEnum.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
## [Introduction](#Introduction)
66

7-
Interface used in order to implements \UnitEnum one.
7+
Interface used in order to act like \UnitEnum one.
88

99
## [Interface synopsis](#Interface-synopsis)
1010

1111
```php
12-
interface SplUnitEnum extends UnitEnum {
13-
/* Inherited methods */
14-
public static UnitEnum::cases(): array
12+
interface SplUnitEnum extends SplEnumerable {
13+
/* Methods */
14+
public static SplUnitEnum::cases(): array
1515
}
1616
```
1717

‎composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"phpcsfixer-check": "./vendor/bin/php-cs-fixer fix --dry-run --diff",
9090
"phpmd": "./vendor/bin/phpmd . github phpmd.xml.dist --exclude 'Tests/*,vendor/*,.history/*'",
9191
"phpstan": "./vendor/bin/phpstan analyse --configuration phpstan.neon.dist",
92-
"psalm": "./vendor/bin/psalm --config=psalm.xml.dist --no-cache --no-file-cache",
92+
"psalm": "./vendor/bin/psalm --config=psalm.xml.dist --no-cache --no-file-cache --shepherd",
9393
"rector": "./vendor/bin/rector process . --dry-run",
9494
"test": [
9595
"@unittest",

‎phpstan.neon.dist

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
parameters:
2-
level: 6
2+
level: 8
33
paths:
44
- .
55
excludePaths:
66
- vendor/*
77
- .history/*
8+
tips:
9+
treatPhpDocTypesAsCertain: false

‎phpunit.xml.dist

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<directory>./Resources</directory>
99
<directory>./Tests</directory>
1010
<directory>./vendor</directory>
11+
<directory>./.history</directory>
1112
<file>./.php-cs-fixer.dist.php</file>
1213
<file>./bootstrap.php</file>
1314
<file>./rector.php</file>

‎psalm.xml.dist

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0"?>
22
<psalm
3-
errorLevel="3"
3+
errorLevel="1"
44
resolveFromConfigFile="true"
55
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
66
xmlns="https://getpsalm.org/schema/config"
@@ -9,17 +9,15 @@
99
findUnusedCode="true"
1010
>
1111
<projectFiles>
12-
<file name="SplBool.php" />
13-
<file name="SplEnum.php" />
14-
<file name="SplFloat.php" />
15-
<file name="SplInt.php" />
16-
<file name="SplString.php" />
17-
<file name="SplType.php" />
18-
<file name="bootstrap.php" />
12+
<directory name="." />
1913
<ignoreFiles>
14+
<file name=".php-cs-fixer.dist.php" />
15+
<file name="bootstrap.php" />
16+
<file name="rector.php" />
2017
<directory name="Resources/stubs" />
2118
<directory name="Tests" />
2219
<directory name="vendor" />
20+
<directory name=".history" />
2321
</ignoreFiles>
2422
</projectFiles>
2523
</psalm>

0 commit comments

Comments
 (0)
Please sign in to comment.