Skip to content

Commit 964d55a

Browse files
gturpin-devbrendt
andauthored
feat(support): add enums support (#878)
Co-authored-by: Brent Roose <[email protected]>
1 parent cfe1564 commit 964d55a

File tree

10 files changed

+878
-5
lines changed

10 files changed

+878
-5
lines changed

src/Tempest/Console/src/HasConsole.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function confirm(
7070
string $question,
7171
bool $default = false,
7272
?string $yes = null,
73-
?string $no = null
73+
?string $no = null,
7474
): bool {
7575
return $this->console->confirm(
7676
question: $question,
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Support;
6+
7+
use ValueError;
8+
use UnitEnum;
9+
use Traversable;
10+
use Tempest\Support\ArrayHelper;
11+
use IteratorAggregate;
12+
use Iterator;
13+
use InvalidArgumentException;
14+
use BackedEnum;
15+
use ArrayIterator;
16+
17+
/**
18+
* This trait provides a bunch of helper methods to work with enums
19+
*
20+
* @template TEnum of \UnitEnum
21+
*/
22+
trait IsEnumHelper
23+
{
24+
/**
25+
* Gets the enum case by name
26+
*
27+
* @throws ValueError if the case name is not valid
28+
*/
29+
public static function fromName(string $name): static
30+
{
31+
return static::tryFromName($name) ?? throw new ValueError(sprintf('"%s" is not a valid case name for enum "%s"', $name, static::class));
32+
}
33+
34+
/**
35+
* Gets the enum case by name or null if the case name is not valid
36+
*/
37+
public static function tryFromName(string $name): ?static
38+
{
39+
$cases = array_filter(
40+
static::cases(),
41+
fn (UnitEnum $case) => $case->name === $name,
42+
);
43+
44+
return array_shift($cases);
45+
}
46+
47+
/**
48+
* Gets the enum case by name, for Pure enums
49+
* This will not override the `from()` method for Backed enums
50+
*/
51+
public static function from(string $case): static
52+
{
53+
return static::fromName($case);
54+
}
55+
56+
/**
57+
* Gets the enum case by name, for Pure enums or null if the case is not valid
58+
* This will not override the `tryFrom()` method for Backed enums
59+
*/
60+
public static function tryFrom(string $case): ?static
61+
{
62+
return static::tryFromName($case);
63+
}
64+
65+
/**
66+
* Check if the current enum case is equal to the given enum
67+
*
68+
* @param UnitEnum $enum The enum to compare
69+
*
70+
* @return bool True if the enums are equal, false otherwise
71+
*/
72+
public function is(UnitEnum $enum): bool
73+
{
74+
return $this === $enum;
75+
}
76+
77+
/**
78+
* Check if the current enum case is not equal to the given enum
79+
*
80+
* @param UnitEnum $enum The enum to compare
81+
*
82+
* @return bool True if the enums are not equal, false otherwise
83+
*/
84+
public function isNot(UnitEnum $enum): bool
85+
{
86+
return ! $this->is($enum);
87+
}
88+
89+
/**
90+
* Check if the current enum case is in the given list of enums
91+
*
92+
* @param Traversable<UnitEnum>|array<UnitEnum> $enums The list of enums to check
93+
*
94+
* @return bool True if the current enum is in the list, false otherwise
95+
*/
96+
public function in(Traversable|array $enums): bool
97+
{
98+
$iterator = match (true) {
99+
is_array($enums) => new ArrayIterator($enums),
100+
$enums instanceof Iterator => $enums,
101+
$enums instanceof IteratorAggregate => $enums->getIterator(),
102+
default => throw new InvalidArgumentException(sprintf('The given value must be an iterable value, "%s" given', get_debug_type($enums))),
103+
};
104+
105+
foreach ($iterator as $enum) {
106+
if ($this->is($enum)) {
107+
return true;
108+
}
109+
}
110+
111+
return false;
112+
}
113+
114+
/**
115+
* Check if the current enum case is not in the given list of enums
116+
*
117+
* @param Traversable<UnitEnum>|array<UnitEnum> $enums The list of enums to check
118+
*
119+
* @return bool True if the current enum is not in the list, false otherwise
120+
*/
121+
public function notIn(Traversable|array $enums): bool
122+
{
123+
return ! $this->in($enums);
124+
}
125+
126+
/**
127+
* Check if the current enum has the name in its cases
128+
*
129+
* @param string $name The enum case name
130+
*
131+
* @return bool True if the name is in the enum, false otherwise
132+
*/
133+
public static function has(string $name): bool
134+
{
135+
$caseNames = array_column(static::cases(), 'name');
136+
137+
return in_array($name, $caseNames, strict: true); /** @phpstan-ignore-line function.impossibleType ( prevent to always evaluate to true/false as in enum context the result is predictable ) */
138+
}
139+
140+
/**
141+
* Check if the current enum does not have the name in its cases
142+
*
143+
* @param string $name The enum case name
144+
*
145+
* @return bool True if the name is not in the enum, false otherwise
146+
*/
147+
public static function hasNot(string $name): bool
148+
{
149+
return ! self::has($name);
150+
}
151+
152+
/**
153+
* Check if the current enum has the value in its cases
154+
* For Pure enums, this method is the equivalent of `has()`
155+
*
156+
* @param string|int $value The value to check
157+
*
158+
* @return bool True if the value is in the enum, false otherwise
159+
*/
160+
public static function hasValue(string|int $value): bool
161+
{
162+
/** @var class-string<TEnum> */
163+
$class = static::class;
164+
165+
$caseValues = is_subclass_of($class, BackedEnum::class)
166+
? array_column(static::cases(), 'value')
167+
: array_column(static::cases(), 'name');
168+
169+
return in_array($value, $caseValues, strict: true); /** @phpstan-ignore-line function.impossibleType ( prevent to always evaluate to true/false as in enum context the result is predictable ) */
170+
}
171+
172+
/**
173+
* Check if the current enum does not have the value in its cases
174+
* For Pure enums, this method is the equivalent of `hasNot()`
175+
*
176+
* @param string|int $value The value to check
177+
*
178+
* @return bool True if the value is not in the enum, false otherwise
179+
*/
180+
public static function hasNotValue(string|int $value): bool
181+
{
182+
return ! self::hasValue($value);
183+
}
184+
185+
/**
186+
* Gets an array of case names
187+
* Both pure and backed enums will return their names
188+
*
189+
* @return array<int, string>
190+
*/
191+
public static function names(): array
192+
{
193+
return array_column(static::cases(), 'name');
194+
}
195+
196+
/**
197+
* Gets an array of case values
198+
* Pure enums will return their names, backed enums will return their values
199+
*
200+
* @return array<int, int|string>
201+
*/
202+
public static function values(): array
203+
{
204+
/** @var class-string<TEnum> */
205+
$class = static::class;
206+
207+
return is_subclass_of($class, BackedEnum::class)
208+
? array_column(static::cases(), 'value')
209+
: array_column(static::cases(), 'name');
210+
}
211+
212+
/**
213+
* Wrap all enum cases in an array
214+
*
215+
* @return ArrayHelper<static>
216+
*/
217+
public static function collect(): ArrayHelper
218+
{
219+
return arr(static::cases());
220+
}
221+
222+
/**
223+
* Returns an associative array of case names and values
224+
* For pure enums, this method is the equivalent of `values()`
225+
*
226+
* @return array<int|string, int|string>
227+
*/
228+
public static function options(): array
229+
{
230+
/** @var class-string<TEnum> */
231+
$class = static::class;
232+
233+
return is_subclass_of($class, BackedEnum::class)
234+
? array_column(static::cases(), 'value', 'name')
235+
: self::values();
236+
}
237+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Support\Tests\Fixtures\Enums;
6+
7+
use Tempest\Support\IsEnumHelper;
8+
9+
enum EmptyEnum: string
10+
{
11+
use IsEnumHelper;
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Support\Tests\Fixtures\Enums;
6+
7+
use Tempest\Support\IsEnumHelper;
8+
9+
enum SampleIntegerBackedEnum: int
10+
{
11+
use IsEnumHelper;
12+
13+
case SUCCESS = 200;
14+
case CREATED = 201;
15+
case NOT_FOUND = 404;
16+
case SERVER_ERROR = 500;
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Support\Tests\Fixtures\Enums;
6+
7+
use Tempest\Support\IsEnumHelper;
8+
9+
enum SampleStatusBackedEnum: string
10+
{
11+
use IsEnumHelper;
12+
13+
case PUBLISH = 'publish';
14+
case DRAFT = 'draft';
15+
case TRASH = 'trash';
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Support\Tests\Fixtures\Enums;
6+
7+
use Tempest\Support\IsEnumHelper;
8+
9+
enum SampleStatusPureEnum
10+
{
11+
use IsEnumHelper;
12+
13+
case PUBLISH;
14+
case DRAFT;
15+
case TRASH;
16+
}

0 commit comments

Comments
 (0)