Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
15 / 15
CRAP
100.00% covered (success)
100.00%
1 / 1
EnumHelper
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
15 / 15
26
100.00% covered (success)
100.00%
1 / 1
 cases
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 names
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 values
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 nameMap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 valueMap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 options
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 hasName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 tryFromName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 labels
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 labelMap
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 unitEnumClass
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 backedEnumClass
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 labeledEnumClass
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Ergonomic utilities for PHP enums, including names, values, lookups, and option maps.
7 *
8 * This file is part of fast-forward/enum project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/enum
14 * @see      https://github.com/php-fast-forward/enum/issues
15 * @see      https://php-fast-forward.github.io/enum/
16 * @see      https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Enum\Helper;
20
21use BackedEnum;
22use UnitEnum;
23use ValueError;
24use FastForward\Enum\LabeledEnumInterface;
25
26 class EnumHelper
27{
28    /**
29     * @template T of UnitEnum
30     *
31     * @param class-string<T>|T $enum
32     *
33     * @return list<T>
34     */
35    public static function cases(string|UnitEnum $enum): array
36    {
37        $enumClass = self::unitEnumClass($enum);
38
39        return $enumClass::cases();
40    }
41
42    /**
43     * @template T of UnitEnum
44     *
45     * @param class-string<T>|T $enum
46     *
47     * @return list<string>
48     */
49    public static function names(string|UnitEnum $enum): array
50    {
51        $enumClass = self::unitEnumClass($enum);
52
53        return array_map(static fn(UnitEnum $case): string => $case->name, $enumClass::cases());
54    }
55
56    /**
57     * @template T of BackedEnum
58     *
59     * @param class-string<T>|T $enum
60     *
61     * @return list<int|string>
62     */
63    public static function values(string|BackedEnum $enum): array
64    {
65        $enumClass = self::backedEnumClass($enum);
66
67        return array_map(static fn(BackedEnum $case): int|string => $case->value, $enumClass::cases());
68    }
69
70    /**
71     * @template T of UnitEnum
72     *
73     * @param class-string<T>|T $enum
74     *
75     * @return array<string, T>
76     */
77    public static function nameMap(string|UnitEnum $enum): array
78    {
79        $enumClass = self::unitEnumClass($enum);
80        $map = [];
81
82        foreach ($enumClass::cases() as $case) {
83            $map[$case->name] = $case;
84        }
85
86        return $map;
87    }
88
89    /**
90     * @template T of BackedEnum
91     *
92     * @param class-string<T>|T $enum
93     *
94     * @return array<int|string, T>
95     */
96    public static function valueMap(string|BackedEnum $enum): array
97    {
98        $enumClass = self::backedEnumClass($enum);
99        $map = [];
100
101        foreach ($enumClass::cases() as $case) {
102            $map[$case->value] = $case;
103        }
104
105        return $map;
106    }
107
108    /**
109     * @template T of BackedEnum
110     *
111     * @param class-string<T>|T $enum
112     *
113     * @return array<string, int|string>
114     */
115    public static function options(string|BackedEnum $enum): array
116    {
117        $enumClass = self::backedEnumClass($enum);
118        $options = [];
119
120        foreach ($enumClass::cases() as $case) {
121            $options[$case->name] = $case->value;
122        }
123
124        return $options;
125    }
126
127    /**
128     * @template T of UnitEnum
129     *
130     * @param class-string<T>|T $enum
131     * @param string $name
132     */
133    public static function hasName(string|UnitEnum $enum, string $name): bool
134    {
135        return isset(self::nameMap($enum)[$name]);
136    }
137
138    /**
139     * @template T of BackedEnum
140     *
141     * @param class-string<T>|T $enum
142     * @param int|string $value
143     */
144    public static function hasValue(string|BackedEnum $enum, int|string $value): bool
145    {
146        $enumClass = self::backedEnumClass($enum);
147
148        return null !== $enumClass::tryFrom($value);
149    }
150
151    /**
152     * @template T of UnitEnum
153     *
154     * @param class-string<T>|T $enum
155     * @param string $name
156     *
157     * @return T|null
158     */
159    public static function tryFromName(string|UnitEnum $enum, string $name): ?UnitEnum
160    {
161        return self::nameMap($enum)[$name] ?? null;
162    }
163
164    /**
165     * @template T of UnitEnum
166     *
167     * @param class-string<T>|T $enum
168     * @param string $name
169     *
170     * @return T
171     */
172    public static function fromName(string|UnitEnum $enum, string $name): UnitEnum
173    {
174        $enumClass = self::unitEnumClass($enum);
175
176        return self::tryFromName($enumClass, $name)
177            ?? throw new ValueError(\sprintf('"%s" is not a valid name for enum %s.', $name, $enumClass));
178    }
179
180    /**
181     * @template T of UnitEnum&LabeledEnumInterface
182     *
183     * @param class-string<T>|T $enum
184     *
185     * @return list<string>
186     */
187    public static function labels(string|UnitEnum $enum): array
188    {
189        $enumClass = self::labeledEnumClass($enum);
190
191        return array_map(static fn(LabeledEnumInterface $case): string => $case->label(), $enumClass::cases());
192    }
193
194    /**
195     * @template T of UnitEnum&LabeledEnumInterface
196     *
197     * @param class-string<T>|T $enum
198     *
199     * @return array<string, string>
200     */
201    public static function labelMap(string|UnitEnum $enum): array
202    {
203        $enumClass = self::labeledEnumClass($enum);
204        $map = [];
205
206        foreach ($enumClass::cases() as $case) {
207            $map[$case->name] = $case->label();
208        }
209
210        return $map;
211    }
212
213    /**
214     * @template T of UnitEnum
215     *
216     * @param class-string<T>|T $enum
217     *
218     * @return class-string<T>
219     */
220    private static function unitEnumClass(string|UnitEnum $enum): string
221    {
222        /** @var class-string<T> $enumClass */
223        $enumClass = \is_string($enum) ? $enum : $enum::class;
224
225        if (! enum_exists($enumClass) || ! is_subclass_of($enumClass, UnitEnum::class, true)) {
226            throw new ValueError(\sprintf('Enum %s must be a unit enum.', $enumClass));
227        }
228
229        return $enumClass;
230    }
231
232    /**
233     * @template T of BackedEnum
234     *
235     * @param class-string<T>|T $enum
236     *
237     * @return class-string<T>
238     */
239    private static function backedEnumClass(string|BackedEnum $enum): string
240    {
241        /** @var class-string<T> $enumClass */
242        $enumClass = \is_string($enum) ? $enum : $enum::class;
243
244        if (! enum_exists($enumClass) || ! is_subclass_of($enumClass, BackedEnum::class, true)) {
245            throw new ValueError(\sprintf('Enum %s must be a backed enum.', $enumClass));
246        }
247
248        return $enumClass;
249    }
250
251    /**
252     * @template T of UnitEnum&LabeledEnumInterface
253     *
254     * @param class-string<T>|T $enum
255     *
256     * @return class-string<T>
257     */
258    private static function labeledEnumClass(string|UnitEnum $enum): string
259    {
260        /** @var class-string<T> $enumClass */
261        $enumClass = self::unitEnumClass($enum);
262
263        if (! is_subclass_of($enumClass, LabeledEnumInterface::class, true)) {
264            throw new ValueError(\sprintf('Enum %s must implement %s.', $enumClass, LabeledEnumInterface::class));
265        }
266
267        return $enumClass;
268    }
269}