Completed
Push — master ( 9a2220...723433 )
by Julián
02:32
created

AbstractEnum::unserialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * enum (https://github.com/phpgears/enum).
5
 * Enum object for PHP.
6
 *
7
 * @license MIT
8
 * @link https://github.com/phpgears/enum
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Gears\Enum;
15
16
use Gears\Enum\Exception\EnumException;
17
use Gears\Enum\Exception\InvalidEnumNameException;
18
use Gears\Enum\Exception\InvalidEnumValueException;
19
use Gears\Immutability\ImmutabilityBehaviour;
20
21
/**
22
 * Base immutable enum class.
23
 */
24
abstract class AbstractEnum implements Enum
25
{
26
    use ImmutabilityBehaviour;
27
28
    /**
29
     * Class is final checked map.
30
     *
31
     * @var bool[]
32
     */
33
    protected static $finalCheckMap = [];
34
35
    /**
36
     * Enum class constants map.
37
     *
38
     * @var array<string, mixed[]>
39
     */
40
    protected static $enumCacheMap = [];
41
42
    /**
43
     * Enum value.
44
     *
45
     * @var mixed
46
     */
47
    private $value;
48
49
    /**
50
     * AbstractEnum constructor.
51
     *
52
     * @param static|mixed $value
53
     *
54
     * @throws EnumException
55
     * @throws InvalidEnumValueException
56
     */
57
    final public function __construct($value)
58
    {
59
        $this->assertImmutable();
60
        $this->assertFinal();
61
62
        $this->checkValue($value);
63
64
        $this->value = $value instanceof Enum ? $value->getValue() : $value;
65
    }
66
67
    /**
68
     * Assert enum is final.
69
     *
70
     * @throws EnumException
71
     */
72
    private function assertFinal(): void
73
    {
74
        $class = static::class;
75
76
        if (isset(static::$finalCheckMap[$class])) {
77
            return;
78
        }
79
80
        if (!(new \ReflectionClass(static::class))->isFinal()) {
81
            throw new EnumException(\sprintf('Enum class "%s" should be final', static::class));
82
        }
83
84
        static::$finalCheckMap[$class] = true;
85
    }
86
87
    /**
88
     * Value based static constructor.
89
     *
90
     * @param string  $name
91
     * @param mixed[] $params
92
     *
93
     * @return self
94
     */
95
    final public static function __callStatic(string $name, array $params)
96
    {
97
        if (\count($params) !== 0) {
98
            throw new EnumException('Enum static constructor must be called with no parameters');
99
        }
100
101
        $enumerator = static::normalizeName($name);
102
        $enumerators = static::getEnumerators();
103
104
        if (!\array_key_exists($enumerator, $enumerators)) {
105
            throw new InvalidEnumNameException(\sprintf(
106
                '"%s" is not a valid enumerator for enum "%s"',
107
                $name,
108
                static::class
109
            ));
110
        }
111
112
        return new static($enumerators[$enumerator]);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    final public function isEqualTo(Enum $enum): bool
119
    {
120
        return \get_class($enum) === static::class && $enum->getValue() === $this->value;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    final public function isAnyOf(array $enums): bool
127
    {
128
        foreach ($enums as $enum) {
129
            if ($this->isEqualTo($enum)) {
130
                return true;
131
            }
132
        }
133
134
        return false;
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    final public function getValue()
141
    {
142
        return $this->value;
143
    }
144
145
    /**
146
     * @return array<string, mixed>
147
     */
148
    final public function __serialize(): array
149
    {
150
        return ['value' => $this->value];
151
    }
152
153
    /**
154
     * @param array<string, mixed> $data
155
     *
156
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
157
     */
158
    final public function __unserialize(array $data): void
159
    {
160
        $this->assertImmutable();
161
        $this->assertFinal();
162
163
        $value = $data['value'];
164
165
        $this->checkValue($value);
166
167
        $this->value = $value;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    final public function serialize(): string
174
    {
175
        return \serialize($this->value);
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     *
181
     * @param mixed $serialized
182
     */
183
    public function unserialize($serialized): void
184
    {
185
        $this->assertImmutable();
186
        $this->assertFinal();
187
188
        $value = \unserialize($serialized, ['allowed_classes' => false]);
189
190
        $this->checkValue($value);
191
192
        $this->value = $value;
193
    }
194
195
    final public function __clone()
196
    {
197
    }
198
199
    /**
200
     * Check enum value validity.
201
     *
202
     * @param static|mixed $value
203
     *
204
     * @throws InvalidEnumValueException
205
     */
206
    private function checkValue($value): void
207
    {
208
        if ($value instanceof Enum) {
209
            if (\get_class($value) !== static::class) {
210
                throw new InvalidEnumValueException(\sprintf(
211
                    'Enum "%s" cannot be created from enum "%s"',
212
                    static::class,
213
                    \get_class($value)
214
                ));
215
            }
216
217
            return;
218
        }
219
220
        if (!\in_array($value, static::getEnumerators(), true)) {
221
            throw new InvalidEnumValueException(\sprintf(
222
                '"%s" is not a valid value for enum "%s"',
223
                $value,
224
                static::class
225
            ));
226
        }
227
    }
228
229
    /**
230
     * Get list of enumerators.
231
     *
232
     * @return array<string, mixed>
233
     */
234
    private static function getEnumerators(): array
235
    {
236
        $class = static::class;
237
238
        if (!isset(static::$enumCacheMap[$class])) {
239
            $constants = [];
240
            foreach ((new \ReflectionClass($class))->getReflectionConstants() as $reflectionConstant) {
241
                if ($reflectionConstant->isPublic()) {
242
                    $constants[static::normalizeName($reflectionConstant->getName())] = $reflectionConstant->getValue();
243
                }
244
            }
245
246
            static::$enumCacheMap[$class] = $constants;
247
        }
248
249
        return static::$enumCacheMap[$class];
250
    }
251
252
    /**
253
     * Normalize name.
254
     *
255
     * @param string $name
256
     *
257
     * @return string
258
     */
259
    private static function normalizeName(string $name): string
260
    {
261
        return \strtoupper($name);
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     *
267
     * @return string[]
268
     */
269
    final protected function getAllowedInterfaces(): array
270
    {
271
        return [Enum::class];
272
    }
273
}
274