-
-
Notifications
You must be signed in to change notification settings - Fork 27
/
ReadableEnumTrait.php
173 lines (146 loc) · 5.64 KB
/
ReadableEnumTrait.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<?php
declare(strict_types=1);
/*
* This file is part of the "elao/enum" package.
*
* Copyright (C) Elao
*
* @author Elao <[email protected]>
*/
namespace Elao\Enum;
use Elao\Enum\Attribute\EnumCase;
use Elao\Enum\Attribute\ReadableEnum;
use Elao\Enum\Exception\LogicException;
use Elao\Enum\Exception\NameException;
trait ReadableEnumTrait
{
use EnumCaseAttributesTrait;
/**
* {@inheritdoc}
*/
public static function readableForValue(string|int $value): string
{
if (!is_a(static::class, \BackedEnum::class, true)) {
throw new \BadMethodCallException(sprintf(
'Cannot call method "%s" on non-backed enum "%s".',
__METHOD__,
static::class,
));
}
/** @var ReadableEnumInterface $case */
$case = static::from($value);
return $case->getReadable();
}
/**
* {@inheritdoc}
*/
public static function readableForName(string $name): string
{
/** @var array<string,ReadableEnumInterface>|null $map */
static $map;
if (null === $map) {
$map = array_combine(array_map(static fn (\UnitEnum $e) => $e->name, static::cases()), static::cases());
}
if (!isset($map[$name])) {
throw new NameException($name, static::class);
}
return $map[$name]->getReadable();
}
/**
* {@inheritdoc}
*/
public function getReadable(): string
{
return static::arrayAccessReadables()[$this];
}
/**
* {@inheritdoc}
*
* Implements readables using PHP 8 attributes, expecting an {@link EnumCase} on each case, with a label,
* or uses the value as label if {@link ReadableEnum} is used on the class.
*/
public static function readables(): iterable
{
static $readables;
if (!isset($readables)) {
$readableEnumAttribute = static::getReadableEnumAttribute();
$readables = new \SplObjectStorage();
$r = new \ReflectionEnum(static::class);
if (($readableEnumAttribute?->useValueAsDefault ?? false) && 'string' !== (string) $r->getBackingType()) {
throw new LogicException(sprintf(
'Cannot use "useValueAsDefault" with "#[%s]" attribute on enum "%s" as it\'s not a string backed enum.',
ReadableEnum::class,
static::class,
));
}
/** @var static $case */
foreach (static::cases() as $case) {
$attribute = $case->getEnumCaseAttribute();
if (null === $attribute && null === $readableEnumAttribute) {
throw new LogicException(sprintf(
'enum "%s" using the "%s" trait must define a "%s" attribute on every cases. Case "%s" is missing one. Alternatively, override the "%s()" method, or use the "%s" attribute on the enum class to use the value as default.',
static::class,
ReadableEnumTrait::class,
EnumCase::class,
$case->name,
__METHOD__,
ReadableEnum::class,
));
}
if (null === $attribute?->label && null === $readableEnumAttribute) {
throw new LogicException(sprintf(
'enum "%s" using the "%s" trait must define a label using the "%s" attribute on every cases. Case "%s" is missing a label. Alternatively, override the "%s()" method, or use the "#[%s]" attribute on the enum class to use the value as default.',
static::class,
ReadableEnumTrait::class,
EnumCase::class,
$case->name,
__METHOD__,
ReadableEnum::class,
));
}
$readables[$case] = sprintf(
'%s%s%s',
$readableEnumAttribute?->prefix,
$attribute?->label ?? ($readableEnumAttribute->useValueAsDefault ? $case->value : $case->name),
$readableEnumAttribute?->suffix,
);
}
}
/** @var static $case */
foreach (static::cases() as $case) {
yield $case => $readables[$case];
}
}
/**
* @internal
*/
private static function getReadableEnumAttribute(): ?ReadableEnum
{
$r = new \ReflectionEnum(static::class);
if (null === $rAttr = $r->getAttributes(ReadableEnum::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
return null;
}
return $rAttr->newInstance();
}
/**
* As objects, {@link https://wiki.php.net/rfc/enumerations#splobjectstorage_and_weakmaps Enum cases cannot be used as keys in an array}.
* However, they can be used as keys in a SplObjectStorage or WeakMap.
* Because they are singletons they never get garbage collected, and thus will never be removed from a WeakMap,
* making these two storage mechanisms effectively equivalent.
*
* However, there is a {@link https://wiki.php.net/rfc/object_keys_in_arrays pending RFC} regarding object as array keys.
*
* @internal
*/
private static function arrayAccessReadables(): \SplObjectStorage
{
static $readables;
if (!isset($readables)) {
$readables = new \SplObjectStorage();
foreach (static::readables() as $case => $label) {
$readables[$case] = $label;
}
}
return $readables;
}
}